[REM] payment(_alipay,_demo,_paypal), website_payment: drop fees support

The "fees" or "extra fees" or "customer fees" feature was meant to make
customers pay for the processing fee charged by the payment provider
they choose to make their payment. The fee was also displayed on the
payment form to deter customers and encourage them to choose another,
cheaper, payment provider.

In practice, it didn't hold up because:
1. the provider's API must allow sending the fee as a separate amount,
   and PayPal was the only supported provider to do it;
2. charging extra fees is highly discouraged by providers, and forbidden
   in Europe;
3. the final fee amount depends on the customer, country, payment
   method, risk profile... rendering charging the actual fees amount
   infeasible;
4. most of the time, only one payment provider is enabled at a time,
   thus alienating customers who would have no other choice than paying
   the fee;

task-3358581

closes odoo/odoo#132104

Related: odoo/documentation#5517
Related: odoo/upgrade#5053
Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
This commit is contained in:
Antoine Vandevenne (anv)
2023-08-19 09:06:59 +02:00
parent 743f174a63
commit d5bee8b86f
23 changed files with 12 additions and 391 deletions
@@ -39,12 +39,6 @@ class PortalAccount(portal.PortalAccount):
partner_sudo, invoice_company
)
fees_by_provider = {
pro_sudo: pro_sudo._compute_fees(
invoice.amount_total, invoice.currency_id, invoice.partner_id.country_id
) for pro_sudo in providers_sudo.filtered('fees_active')
}
portal_page_values = {
'company_mismatch': company_mismatch,
'expected_company': invoice_company,
@@ -52,7 +46,6 @@ class PortalAccount(portal.PortalAccount):
payment_form_values = {
'providers': providers_sudo,
'tokens': tokens,
'fees_by_provider': fees_by_provider,
'show_tokenize_input': PaymentPortal._compute_show_tokenize_input_mapping(
providers_sudo
),
-7
View File
@@ -119,12 +119,6 @@ class PaymentPortal(portal.CustomerPortal):
# 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)
# Compute the fees taken by providers supporting the feature
fees_by_provider = {
provider_sudo: provider_sudo._compute_fees(amount, currency, partner_sudo.country_id)
for provider_sudo in providers_sudo.filtered('fees_active')
}
# Generate a new access token in case the partner id or the currency id was updated
access_token = payment_utils.generate_access_token(partner_sudo.id, amount, currency.id)
@@ -136,7 +130,6 @@ class PaymentPortal(portal.CustomerPortal):
payment_form_values = {
'providers': providers_sudo,
'tokens': tokens_sudo,
'fees_by_provider': fees_by_provider,
'show_tokenize_input': self._compute_show_tokenize_input_mapping(
providers_sudo, **kwargs
),
-65
View File
@@ -123,17 +123,6 @@ class PaymentProvider(models.Model):
currency_field='main_currency_id',
)
# Fees fields
fees_active = fields.Boolean(string="Add Extra Fees")
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(
string="Displayed as", help="Description of the provider for customers",
@@ -177,9 +166,6 @@ class PaymentProvider(models.Model):
selection=[('full_only', "Full Only"), ('partial', "Partial")],
compute='_compute_feature_support_fields',
)
support_fees = fields.Boolean(
string="Fees Supported", compute='_compute_feature_support_fields'
)
# Kanban view fields
image_128 = fields.Image(string="Image", max_width=128, max_height=128)
@@ -286,7 +272,6 @@ class PaymentProvider(models.Model):
- `support_express_checkout`: Whether the "express checkout" feature is supported. `False`
by default.
- `support_fees`: Whether the "extra fees" feature is supported. `False` by default.
- `support_manual_capture`: Whether the "manual capture" feature is supported. `False` by
default.
- `support_refund`: Which type of the "refunds" feature is supported: `None`,
@@ -302,7 +287,6 @@ class PaymentProvider(models.Model):
"""
self.update(dict.fromkeys((
'support_express_checkout',
'support_fees',
'support_manual_capture',
'support_refund',
'support_tokenization',
@@ -343,21 +327,6 @@ class PaymentProvider(models.Model):
}
}
#=== CONSTRAINT METHODS ===#
@api.constrains('fees_dom_var', 'fees_int_var')
def _check_fee_var_within_boundaries(self):
""" Check that variable fees are within realistic boundaries.
Variable fee values should always be positive and below 100% to respectively avoid negative
and infinite (division by zero) fee amounts.
:return None
"""
for provider in self:
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 ===#
@api.model_create_multi
@@ -573,40 +542,6 @@ class PaymentProvider(models.Model):
"""
return True
def _compute_fees(self, amount, currency, country):
""" Compute the transaction fees.
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 + 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.
:param float amount: The amount to pay for the transaction.
:param recordset currency: The currency of the transaction, as a `res.currency` record.
:param recordset country: The customer country, as a `res.country` record.
:return: The computed fees.
:rtype: float
"""
self.ensure_one()
fees = 0.0
if self.fees_active:
if country == self.company_id.country_id:
fixed = self.fees_dom_fixed
variable = self.fees_dom_var
else:
fixed = self.fees_int_fixed
variable = self.fees_int_var
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):
""" Return the amount to use for validation operations.
+4 -16
View File
@@ -45,9 +45,6 @@ class PaymentTransaction(models.Model):
string="Amount", currency_field='currency_id', readonly=True, required=True)
currency_id = fields.Many2one(
string="Currency", comodel_name='res.currency', readonly=True, required=True)
fees = fields.Monetary(
string="Fees", currency_field='currency_id',
help="The fees amount; set by the system as it depends on the provider", readonly=True)
token_id = fields.Many2one(
string="Payment Token", comodel_name='payment.token', readonly=True,
domain='[("provider_id", "=", "provider_id")]', ondelete='restrict')
@@ -187,15 +184,6 @@ class PaymentTransaction(models.Model):
'partner_phone': partner.phone,
})
# Compute fees. For validation transactions, fees are zero.
if values.get('operation') == 'validation':
values['fees'] = 0
else:
currency = self.env['res.currency'].browse(values.get('currency_id')).exists()
values['fees'] = provider._compute_fees(
values.get('amount', 0), currency, partner.country_id,
)
# Include provider-specific create values
values.update(self._get_specific_create_values(provider.code, values))
@@ -212,11 +200,11 @@ class PaymentTransaction(models.Model):
# can lead to inconsistent string representation of the amounts sent to the providers.
# E.g., tx.create(amount=1111.11) -> tx.amount == 1111.1100000000001
# To ensure a proper string representation, we invalidate this request's cache values of the
# `amount` and `fees` fields for the created transactions. This forces the ORM to read the
# values from the DB where there were stored using `float_repr`, which produces a result
# consistent with the format expected by providers.
# `amount` field for the created transactions. This forces the ORM to read the values from
# the DB where there were stored using `float_repr`, which produces a result consistent with
# the format expected by providers.
# E.g., tx.create(amount=1111.11) ; tx.invalidate_recordset() -> tx.amount == 1111.11
txs.invalidate_recordset(['amount', 'fees'])
txs.invalidate_recordset(['amount'])
return txs
@@ -97,59 +97,3 @@ class TestPaymentProvider(PaymentCommon):
self.company.id, self.partner.id, self.amount, currency_id=self.currency_euro.id
)
self.assertIn(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.provider.company_id.currency_id,
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. """
# Some modules change the company settings and may make this test useless.
if self.currency_euro == self.provider.main_currency_id or (
self.country_france == self.provider.company_id.country_id
):
return
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.provider.main_currency_id.id: 1.},
):
transaction_fees = self.currency.round(
self.provider._compute_fees(self.amount, self.currency_euro, self.country_france)
)
self.assertEqual(transaction_fees, 2.)
@@ -9,7 +9,6 @@
<field name="company_id" invisible="1"/>
<field name="is_published" invisible="1"/>
<field name="main_currency_id" invisible="1"/>
<field name="support_fees" invisible="1"/>
<field name="support_manual_capture" invisible="1"/>
<field name="support_tokenization" invisible="1"/>
<field name="support_express_checkout" invisible="1"/>
@@ -108,23 +107,6 @@
<group string="Payment Followup" name="payment_followup" invisible="1"/>
</group>
</page>
<page string="Fees" name="fees" invisible="not support_fees">
<group name="payment_fees">
<group class="oe_inline">
<field name="fees_active"/>
<field name="fees_dom_fixed"
invisible="not fees_active"/>
<field name="fees_dom_var"
widget="percentage"
invisible="not fees_active"/>
<field name="fees_int_fixed"
invisible="not fees_active"/>
<field name="fees_int_var"
widget="percentage"
invisible="not fees_active"/>
</group>
</group>
</page>
<page string="Messages"
name="messages"
invisible="module_id and module_state != 'installed'">
@@ -22,7 +22,6 @@
- 'providers' - The payment providers compatible with the current transaction
- 'tokens' - The payment tokens of the current partner and payment providers
- 'default_token_id' - The id of the token that should be pre-selected. Optional
- 'fees_by_provider' - The dict of transaction fees for each provider. Optional
- 'show_tokenize_input' - Whether the option to save the payment method is shown
- 'reference_prefix' - The custom prefix to compute the full transaction reference
- 'amount' - The amount to pay. Optional (sale_subscription)
@@ -52,7 +51,6 @@
t-value="default_token_id if default_token_id and token_count > 0
else providers[0].id if provider_count == 1 and token_count == 0
else None"/>
<t t-set="fees_by_provider" t-value="fees_by_provider or dict()"/>
<t t-set="footer_template_id"
t-value="footer_template_id or 'payment.footer'"/>
@@ -75,13 +73,6 @@
<span class="payment_option_name" t-esc="token.display_name"/>
<!-- === "V" check mark === -->
<t t-call="payment.verified_token_checkmark"/>
<!-- === "Fees" badge === -->
<span t-if="fees_by_provider.get(provider)"
class="badge rounded-pill text-bg-secondary ms-1">
+ <t t-esc="fees_by_provider.get(provider)"
t-options="{'widget': 'monetary', 'display_currency': currency}"/>
Fees
</span>
<!-- === "Test token" badge === -->
<t t-call="payment.test_token_badge"/>
<!-- === "Unpublished" badge === -->
@@ -142,14 +133,6 @@
class="badge rounded-pill text-bg-danger ms-1">
Unpublished
</span>
<!-- === Extra fees badge === -->
<t t-if="fees_by_provider.get(provider)">
<span class="badge rounded-pill text-bg-secondary ms-1">
+ <t t-esc="fees_by_provider.get(provider)"
t-options="{'widget': 'monetary', 'display_currency': currency}"/>
Fees
</span>
</t>
</label>
<!-- === Payment method icons list === -->
<t t-call="payment.icon_list"/>
@@ -28,7 +28,6 @@
<field name="source_transaction_id"
invisible="not source_transaction_id"/>
<field name="amount"/>
<field name="fees" invisible="fees == 0.0"/>
<field name="currency_id" invisible="1"/>
<field name="provider_id"/>
<field name="company_id" groups="base.group_multi_company"/>
@@ -78,7 +77,6 @@
<!-- Needed to display the currency of the amounts -->
<field name="currency_id" column_invisible="True"/>
<field name="amount"/>
<field name="fees"/>
<field name="state"/>
<field name="company_id" groups="base.group_multi_company" optional="show"/>
</tree>
@@ -28,15 +28,6 @@ class PaymentProvider(models.Model):
alipay_seller_email = fields.Char(
string="Alipay Seller Email", help="The public Alipay partner email")
#=== COMPUTE METHODS ===#
def _compute_feature_support_fields(self):
""" Override of `payment` to enable additional features. """
super()._compute_feature_support_fields()
self.filtered(lambda p: p.code == 'alipay').update({
'support_fees': True,
})
# === BUSINESS METHODS ===#
@api.model
@@ -30,13 +30,6 @@ class PaymentTransaction(models.Model):
return res
base_url = self.provider_id.get_base_url()
if self.fees:
# Similarly to what is done in `payment::payment.transaction.create`, we need to round
# the sum of the amount and of the fees to avoid inconsistent string representations.
# E.g., str(1111.11 + 7.09) == '1118.1999999999998'
total_fee = self.currency_id.round(self.amount + self.fees)
else:
total_fee = self.amount
rendering_values = {
'_input_charset': 'utf-8',
'notify_url': urls.url_join(base_url, AlipayController._webhook_url),
@@ -44,7 +37,7 @@ class PaymentTransaction(models.Model):
'partner': self.provider_id.alipay_merchant_partner_id,
'return_url': urls.url_join(base_url, AlipayController._return_url),
'subject': self.reference,
'total_fee': f'{total_fee:.2f}',
'total_fee': f'{self.amount:.2f}',
}
if self.provider_id.alipay_payment_method == 'standard_checkout':
# https://global.alipay.com/docs/ac/global/create_forex_trade
-1
View File
@@ -14,7 +14,6 @@ class AlipayCommon(PaymentCommon):
'alipay_merchant_partner_id': 'dummy',
'alipay_md5_signature_key': 'dummy',
'alipay_seller_email': 'dummy',
'fees_active': False, # Only activate fees in dedicated tests
})
# override defaults for helpers
+1 -30
View File
@@ -55,7 +55,7 @@ class AlipayTest(AlipayCommon, PaymentHttpCommon):
'partner': self.alipay.alipay_merchant_partner_id,
'return_url': self._build_url(AlipayController._return_url),
'subject': self.reference,
'total_fee': str(self.amount), # Fees disabled by default
'total_fee': str(self.amount),
}
if self.alipay.alipay_payment_method == 'standard_checkout':
@@ -91,35 +91,6 @@ class AlipayTest(AlipayCommon, PaymentHttpCommon):
"Alipay: invalid inputs specified in the redirect form.",
)
def test_03_redirect_form_with_fees(self):
# update provider: compute fees
self.alipay.write({
'fees_active': True,
'fees_dom_fixed': 1.0,
'fees_dom_var': 0.0035,
'fees_int_fixed': 1.5,
'fees_int_var': 0.005,
})
transaction_fees = self.currency.round(
self.alipay._compute_fees(
self.amount,
self.currency,
self.partner.country_id,
)
)
self.assertEqual(transaction_fees, 18.78)
total_fee = self.currency.round(self.amount + transaction_fees)
self.assertEqual(total_fee, 1129.89)
tx = self._create_transaction(flow='redirect')
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'])
self.assertEqual(redirect_form_data['inputs']['total_fee'], f'{total_fee:.2f}')
def test_21_standard_checkout_feedback(self):
self.alipay.alipay_payment_method = 'standard_checkout'
self.currency = self.currency_euro
@@ -24,7 +24,6 @@ class PaymentProvider(models.Model):
""" Override of `payment` to enable additional features. """
super()._compute_feature_support_fields()
self.filtered(lambda p: p.code == 'demo').update({
'support_fees': True,
'support_manual_capture': 'partial',
'support_refund': 'partial',
'support_tokenization': True,
@@ -24,15 +24,6 @@ class PaymentProvider(models.Model):
)
paypal_pdt_token = fields.Char(string="PDT Identity Token", groups='base.group_system')
#=== COMPUTE METHODS ===#
def _compute_feature_support_fields(self):
""" Override of `payment` to enable additional features. """
super()._compute_feature_support_fields()
self.filtered(lambda p: p.code == 'paypal').update({
'support_fees': True,
})
#=== BUSINESS METHODS ===#
def _get_supported_currencies(self):
@@ -51,7 +51,6 @@ class PaymentTransaction(models.Model):
'currency_code': self.currency_id.name,
'email': self.partner_email,
'first_name': partner_first_name,
'handling': self.fees,
'item_name': f"{self.company_id.name}: {self.reference}",
'item_number': self.reference,
'last_name': partner_last_name,
-1
View File
@@ -11,7 +11,6 @@ class PaypalCommon(PaymentCommon):
cls.paypal = cls._prepare_provider('paypal', update_values={
'paypal_email_account': 'dummy@test.mail.com',
'fees_active': False,
})
# Override default values
+1 -44
View File
@@ -23,7 +23,7 @@ class PaypalTest(PaypalCommon, PaymentHttpCommon):
'tx_ref': self.reference,
'access_token': self._generate_test_access_token(self.reference),
}
values = {
return {
'address1': 'Huge Street 2/543',
'amount': str(self.amount),
'business': self.paypal.paypal_email_account,
@@ -44,14 +44,6 @@ class PaypalTest(PaypalCommon, PaymentHttpCommon):
'zip': '1000',
}
if self.paypal.fees_active:
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'] = float_repr(fees, self.currency.decimal_places)
return values
@mute_logger('odoo.addons.payment.models.payment_transaction')
def test_redirect_form_values(self):
tx = self._create_transaction(flow='redirect')
@@ -72,29 +64,6 @@ class PaypalTest(PaypalCommon, PaymentHttpCommon):
"Paypal: invalid inputs specified in the redirect form.",
)
@mute_logger('odoo.addons.payment.models.payment_transaction')
def test_redirect_form_with_fees(self):
self.paypal.write({
'fees_active': True,
'fees_dom_fixed': 1.0,
'fees_dom_var': 0.35,
'fees_int_fixed': 1.5,
'fees_int_var': 0.50,
})
expected_values = self._get_expected_values()
tx = self._create_transaction(flow='redirect')
with patch(
'odoo.addons.payment.utils.generate_access_token', new=self._generate_test_access_token
):
processing_values = tx._get_processing_values()
form_info = self._extract_values_from_html_form(processing_values['redirect_form_html'])
self.assertEqual(form_info['action'], 'https://www.sandbox.paypal.com/cgi-bin/webscr')
self.assertDictEqual(
expected_values, form_info['inputs'],
"Paypal: invalid inputs specified in the redirect form.")
def test_feedback_processing(self):
# Unknown transaction
with self.assertRaises(ValidationError):
@@ -119,18 +88,6 @@ class PaypalTest(PaypalCommon, PaymentHttpCommon):
self.assertEqual(tx.state, 'pending')
self.assertEqual(tx.state_message, payload['pending_reason'])
def test_fees_computation(self):
# If the merchant needs to keep 100€, the transaction will be equal to 103.30€.
# In this way, Paypal will take 103.30 * 2.9% + 0.30 = 3.30€
# And the merchant will take 103.30 - 3.30 = 100€
self.paypal.write({
'fees_active': True,
'fees_int_fixed': 0.30,
'fees_int_var': 0.029,
})
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):
""" Test that the notification data are parsed from the content of a validation response."""
response_content = 'SUCCESS\nkey1=val1\nkey2=val+2\n'
@@ -14,8 +14,6 @@
<input type="hidden" name="currency_code" t-att-value="currency_code"/>
<input type="hidden" name="email" t-att-value="email"/>
<input type="hidden" name="first_name" t-att-value="first_name"/>
<input t-if="handling"
type="hidden" name="handling" t-att-value="handling"/>
<input type="hidden" name="item_name" t-att-value="item_name"/>
<input type="hidden" name="item_number" t-att-value="item_number"/>
<input type="hidden" name="last_name" t-att-value="last_name"/>
@@ -128,13 +128,6 @@ class PaymentPortal(payment_portal.PaymentPortal):
providers_sudo = self._get_allowed_providers_sudo(pos_order_sudo, partner_sudo.id, amount_to_pay)
# Compute the fees taken by providers supporting the feature
fees_by_provider = {
provider_sudo: provider_sudo._compute_fees(
amount_to_pay, currency_id, partner_sudo.country_id)
for provider_sudo in providers_sudo.filtered('fees_active')
}
if logged_in:
tokens_sudo = request.env['payment.token']._get_available_tokens(
providers_sudo.ids, partner_sudo.id)
@@ -147,7 +140,6 @@ class PaymentPortal(payment_portal.PaymentPortal):
rendering_context.update({
'providers': providers_sudo,
'tokens': tokens_sudo,
'fees_by_provider': fees_by_provider,
'show_tokenize_input': show_tokenize_input,
**self._get_custom_rendering_context_values(**kwargs),
})
-6
View File
@@ -189,17 +189,11 @@ class CustomerPortal(portal.CustomerPortal):
sale_order_id=order_sudo.id,
**kwargs,
) # In sudo mode to read the fields of providers and partner (if not logged in).
fees_by_provider = {
provider: provider._compute_fees(
amount, currency, partner.country_id
) for provider in providers_sudo.filtered('fees_active')
}
payment_form_values = {
'providers': providers_sudo,
'tokens': request.env['payment.token']._get_available_tokens(
providers_sudo.ids, partner.id, **kwargs
),
'fees_by_provider': fees_by_provider,
'show_tokenize_input': PaymentPortal._compute_show_tokenize_input_mapping(
providers_sudo, sale_order_id=order_sudo.id
),
@@ -33,19 +33,6 @@ class PaymentPortal(payment_portal.PaymentPortal):
return self.payment_pay(**kwargs)
@http.route('/donation/get_provider_fees', type='json', auth='public', website=True, sitemap=False)
def get_provider_fees(self, provider_ids=None, amount=None, currency_id=None, country_id=None):
providers_sudo = request.env['payment.provider'].sudo().browse(provider_ids)
currency = request.env['res.currency'].browse(currency_id)
country = request.env['res.country'].browse(country_id)
# Compute the fees taken by providers supporting the feature
fees_by_provider = {
pro_sudo.id: pro_sudo._compute_fees(amount, currency, country)
for pro_sudo in providers_sudo.filtered('fees_active')
}
return fees_by_provider
@http.route('/donation/transaction/<minimum_amount>', type='json', auth='public', website=True, sitemap=False)
def donation_transaction(self, amount, currency_id, partner_id, access_token, minimum_amount=0, **kwargs):
if float(amount) < float(minimum_amount):
@@ -4,9 +4,6 @@ import core, { _t } from "@web/legacy/js/services/core";
import checkoutForm from '@payment/js/checkout_form';
checkoutForm.include({
events: Object.assign({}, checkoutForm.prototype.events || {}, {
'change .o_wpayment_fee_impact': '_onFeeParameterChange',
}),
/**
* @override
@@ -102,60 +99,5 @@ checkoutForm.include({
_updateShippingCost: function (amount) {
this.txContext.amount = amount;
},
/**
* Update the fees associated to each provider.
*
* Called upon change of any parameter that might impact the fees (marked with
* .o_wpayment_fee_impact).
*
* @private
* @param {Event} ev
* @return {undefined}
*/
_onFeeParameterChange: function (ev) {
const targetId = ev.target.id;
if (targetId.indexOf("amount") >= 0) {
this.txContext.amount = ev.target.value;
}
const providerIds = [];
for (const card of this.$('.o_payment_option_card:has(.o_payment_fee)')) {
const radio = $(card).find('input[name="o_payment_radio"]');
if (radio.data("paymentOptionType") === 'provider') {
providerIds.push(radio.data("paymentOptionId"));
}
}
const countryId = this.$('select[name="country_id"]').val();
if (providerIds && this.txContext.amount) {
this._rpc({
route: '/donation/get_provider_fees',
params: {
'provider_ids': providerIds,
'amount': this.txContext.amount !== undefined
? parseFloat(this.txContext.amount) : null,
'currency_id': this.txContext.currencyId
? parseInt(this.txContext.currencyId) : null,
'country_id': countryId,
},
}).then(feesPerProvider => {
for (const card of this.$('.o_payment_option_card:has(.o_payment_fee)')) {
const radio = $(card).find('input[name="o_payment_radio"]');
let providerId;
if (radio.data("paymentOptionType") === 'provider') {
providerId = radio.data("paymentOptionId");
} else { // token
providerId = radio.data("paymentProviderId");
}
const chunk = $(card).find('.o_payment_fee .oe_currency_value')[0];
chunk.innerText = (feesPerProvider[providerId] || 0).toFixed(2);
}
}).guardedCatch(error => {
error.event.preventDefault();
this._displayError(
_t("Server Error"),
_t("We could not obtain payment fees."),
error.message.data.message
);
});
}
},
});
@@ -27,7 +27,7 @@
<label class="col-form-label fw-bold" for="country_id">Country
<span class="s_website_form_mark"> *</span>
</label>
<select t-att-disabled="'1' if country_id and partner_id else None" id="country_id" name="country_id" t-attf-class="o_wpayment_fee_impact form-select #{error.get('country_id') and 'is-invalid' or ''}">
<select t-att-disabled="'1' if country_id and partner_id else None" id="country_id" name="country_id" t-attf-class="form-select #{error.get('country_id') and 'is-invalid' or ''}">
<option value="">Country...</option>
<t t-foreach="countries" t-as="c">
<option t-att-value="c.id" t-att-selected="c.id == (country_id or -1)">
@@ -47,7 +47,7 @@
<div class="form-check my-2">
<t t-set="is_checked" t-value="float(amount) == float(donation_amount)"/>
<t t-set="has_checked" t-value="has_checked or is_checked"/>
<input class="o_wpayment_fee_impact form-check-input" type="radio" name="amount"
<input class="form-check-input" type="radio" name="amount"
t-attf-id="amount_#{donation_amount_index}" t-att-value="donation_amount"
t-att-checked="is_checked or None"/>
<label class="form-check-label mt-0" t-attf-for="amount_#{donation_amount_index}">
@@ -59,7 +59,7 @@
</div>
</t>
<div t-attf-class="form-check my-2 #{not donation_layout and 'd-none' or ''}">
<input class="o_wpayment_fee_impact form-check-input" type="radio" id="other_amount" name="amount"
<input class="form-check-input" type="radio" id="other_amount" name="amount"
t-att-value="amount" t-att-checked="not has_checked or None"/>
<label class="form-check-label mt-0 d-block" for="other_amount">
<t t-call="website_payment.donation_input">
@@ -86,13 +86,6 @@
<h3 class="o_page_header mt16 mb4">Payment Details</h3>
</t>
</xpath>
<!-- Make fee badges recognizable so that they can be updated upon amount changes -->
<xpath expr="//t[@t-if='fees_by_provider.get(provider)']/span" position="attributes">
<attribute name="class" add="o_payment_fee" separator=" "/>
</xpath>
<xpath expr="//span[@t-if='fees_by_provider.get(provider)']" position="attributes">
<attribute name="class" add="o_payment_fee" separator=" "/>
</xpath>
<!-- Allow to find the provider id from the radio of tokens -->
<xpath expr="//t[@t-foreach='tokens']//input[@name='o_payment_radio']" position="attributes">
<attribute name="t-att-data-payment-provider-id">provider.id</attribute>
@@ -139,7 +132,7 @@
</template>
<template id="website_payment.donation_input" name="Donation input">
<input type="number" class="o_wpayment_fee_impact form-control o_amount_input" t-att-min="donation_options.get('minimumAmount')"
<input type="number" class="form-control o_amount_input" t-att-min="donation_options.get('minimumAmount')"
t-att-max="donation_options.get('maximumAmount')" t-att-value="amount" placeholder="Custom Amount" id="other_amount_value"/>
</template>
</odoo>