[FIX] payment(_*): convert fixed fees into payment currency

Payment fees appeared as float (and not monetary) in the Payment
provider form and were not converted into the chosen payment
currency.

Some tests check the `_compute_fees` function.

task-2854143

closes odoo/odoo#100156

Related: odoo/upgrade#4106
Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
This commit is contained in:
Valentin Vallaeys (vava)
2022-12-15 11:30:47 +01:00
parent dfd5eea60b
commit 7ebfced928
6 changed files with 92 additions and 26 deletions
+16 -9
View File
@@ -111,10 +111,14 @@ class PaymentProvider(models.Model):
# Fees fields
fees_active = fields.Boolean(string="Add Extra Fees")
fees_dom_fixed = fields.Float(string="Fixed domestic fees")
fees_dom_var = fields.Float(string="Variable domestic fees (in percents)")
fees_int_fixed = fields.Float(string="Fixed international fees")
fees_int_var = fields.Float(string="Variable international fees (in percents)")
fees_dom_fixed = fields.Monetary(
string="Fixed domestic fees", currency_field='main_currency_id'
)
fees_dom_var = fields.Float(string="Variable domestic fees")
fees_int_fixed = fields.Monetary(
string="Fixed international fees", currency_field='main_currency_id'
)
fees_int_var = fields.Float(string="Variable international fees")
# Message fields
display_as = fields.Char(
@@ -315,7 +319,7 @@ class PaymentProvider(models.Model):
:return None
"""
for provider in self:
if any(not 0 <= fee < 100 for fee in (provider.fees_dom_var, provider.fees_int_var)):
if any(not 0 <= fee < 1 for fee in (provider.fees_dom_var, provider.fees_int_var)):
raise ValidationError(_("Variable fees must always be positive and below 100%."))
#=== CRUD METHODS ===#
@@ -499,9 +503,9 @@ class PaymentProvider(models.Model):
The computation is based on the fields `fees_dom_fixed`, `fees_dom_var`, `fees_int_fixed`
and `fees_int_var`, and is performed with the formula
:code:`fees = (amount * variable / 100.0 + fixed) / (1 - variable / 100.0)` where the values
of `fixed` and `variable` are taken from either the domestic (`dom`) or international
(`int`) fields, depending on whether the country matches the company's country.
:code:`fees = (amount * variable + fixed) / (1 - variable)` where the values of `fixed` and
`variable` are taken from either the domestic (`dom`) or international (`int`) fields,
depending on whether the country matches the company's country.
For a provider to base the computation on different variables, or to use a different
formula, it must override this method and return the resulting fees.
@@ -522,7 +526,10 @@ class PaymentProvider(models.Model):
else:
fixed = self.fees_int_fixed
variable = self.fees_int_var
fees = (amount * variable / 100.0 + fixed) / (1 - variable / 100.0)
fixed_converted = self.main_currency_id._convert(
fixed, currency, self.company_id, fields.Date.context_today(self)
)
fees = (amount * variable + fixed_converted) / (1 - variable)
return fees
def _get_validation_amount(self):
+2 -2
View File
@@ -6,13 +6,13 @@ from unittest.mock import patch
from lxml import objectify
from odoo.fields import Command
from odoo.tests.common import TransactionCase
from odoo.addons.base.tests.common import BaseCommon
from odoo.tools.misc import hmac as hmac_tool
_logger = logging.getLogger(__name__)
class PaymentCommon(TransactionCase):
class PaymentCommon(BaseCommon):
@classmethod
def setUpClass(cls):
+52 -1
View File
@@ -1,12 +1,14 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from unittest.mock import patch
from odoo.tests import tagged
from odoo.addons.payment.tests.common import PaymentCommon
@tagged('-at_install', 'post_install')
class TestPaymentprovider(PaymentCommon):
class TestPaymentProvider(PaymentCommon):
def test_published_provider_compatible_with_all_users(self):
""" Test that a published provider is always available to all users. """
@@ -67,3 +69,52 @@ class TestPaymentprovider(PaymentCommon):
self.company.id, self.partner.id, self.amount, currency_id=self.env.company.currency_id.id,
)
self.assertNotIn(self.provider, compatible_providers)
def test_fees_null_when_deactivated(self):
""" Test that extra fees of a provider are null if these fees are deactivated. """
self.provider.write({
'fees_active': False,
'fees_dom_fixed': 1.0,
'fees_dom_var': 0.35,
'fees_int_fixed': 0.1,
'fees_int_var': 0.2,
})
transaction_fees = self.provider._compute_fees(
self.amount, self.currency, self.partner.country_id
)
self.assertEqual(transaction_fees, 0)
def test_fees_formula(self):
""" Test that the extra fees of a provider are correctly computed in a domestic case. """
self.provider.write({
'fees_active': True,
'fees_dom_fixed': 1.0,
'fees_dom_var': 0.0035,
'fees_int_fixed': 0.,
'fees_int_var': 0.,
})
transaction_fees = self.currency.round(
self.provider._compute_fees(
self.amount, self.currency_usd, self.provider.company_id.country_id
)
)
self.assertEqual(transaction_fees, 4.91)
def test_fees_with_currency_conversion(self):
""" Test that the conversion of the fees is correctly computed. """
self.provider.write({
'fees_active': True,
'fees_dom_fixed': 0.,
'fees_dom_var': 0.,
'fees_int_fixed': 1.,
'fees_int_var': 0.,
})
with patch(
'odoo.addons.base.models.res_currency.Currency._get_rates',
return_value={self.currency_euro.id: 2., self.currency_usd.id: 1.},
):
transaction_fees = self.currency.round(
self.provider._compute_fees(self.amount, self.currency_euro, self.country_france)
)
self.assertEqual(transaction_fees, 2.)
@@ -94,11 +94,19 @@
</page>
<page string="Fees" name="fees" attrs="{'invisible': [('support_fees', '=', False)]}">
<group name="payment_fees">
<field name="fees_active"/>
<field name="fees_dom_fixed" attrs="{'invisible': [('fees_active', '=', False)]}"/>
<field name="fees_dom_var" attrs="{'invisible': [('fees_active', '=', False)]}"/>
<field name="fees_int_fixed" attrs="{'invisible': [('fees_active', '=', False)]}"/>
<field name="fees_int_var" attrs="{'invisible': [('fees_active', '=', False)]}"/>
<group class="oe_inline">
<field name="fees_active"/>
<field name="fees_dom_fixed"
attrs="{'invisible': [('fees_active', '=', False)]}"/>
<field name="fees_dom_var"
widget="percentage"
attrs="{'invisible': [('fees_active', '=', False)]}"/>
<field name="fees_int_fixed"
attrs="{'invisible': [('fees_active', '=', False)]}"/>
<field name="fees_int_var"
widget="percentage"
attrs="{'invisible': [('fees_active', '=', False)]}"/>
</group>
</group>
</page>
<page string="Messages"
+5 -5
View File
@@ -96,9 +96,9 @@ class AlipayTest(AlipayCommon, PaymentHttpCommon):
self.alipay.write({
'fees_active': True,
'fees_dom_fixed': 1.0,
'fees_dom_var': 0.35,
'fees_dom_var': 0.0035,
'fees_int_fixed': 1.5,
'fees_int_var': 0.50,
'fees_int_var': 0.005,
})
transaction_fees = self.currency.round(
@@ -108,12 +108,12 @@ class AlipayTest(AlipayCommon, PaymentHttpCommon):
self.partner.country_id,
)
)
self.assertEqual(transaction_fees, 7.09)
self.assertEqual(transaction_fees, 18.78)
total_fee = self.currency.round(self.amount + transaction_fees)
self.assertEqual(total_fee, 1118.2)
self.assertEqual(total_fee, 1129.89)
tx = self._create_transaction(flow='redirect')
self.assertEqual(tx.fees, 7.09)
self.assertEqual(tx.fees, 18.78)
with mute_logger('odoo.addons.payment.models.payment_transaction'):
processing_values = tx._get_processing_values()
redirect_form_data = self._extract_values_from_html_form(processing_values['redirect_form_html'])
+4 -4
View File
@@ -4,7 +4,7 @@ from unittest.mock import patch
from odoo.exceptions import ValidationError
from odoo.tests import tagged
from odoo.tools import mute_logger
from odoo.tools import float_repr, mute_logger
from odoo.addons.payment.tests.http_common import PaymentHttpCommon
from odoo.addons.payment_paypal.controllers.main import PaypalController
@@ -41,7 +41,7 @@ class PaypalTest(PaypalCommon, PaymentHttpCommon):
fees = self.currency.round(self.paypal._compute_fees(self.amount, self.currency, self.partner.country_id))
if fees:
# handling input is only specified if truthy
values['handling'] = str(fees)
values['handling'] = float_repr(fees, self.currency.decimal_places)
return values
@@ -111,9 +111,9 @@ class PaypalTest(PaypalCommon, PaymentHttpCommon):
self.paypal.write({
'fees_active': True,
'fees_int_fixed': 0.30,
'fees_int_var': 2.90,
'fees_int_var': 0.029,
})
total_fee = self.paypal._compute_fees(100, False, False)
total_fee = self.paypal._compute_fees(100, self.paypal.main_currency_id, False)
self.assertEqual(round(total_fee, 2), 3.3, 'Wrong computation of the Paypal fees')
def test_parsing_pdt_validation_response_returns_notification_data(self):