[FIX] payment(_stripe): adapt validation currency to payment method
When payment details are tokenized through a validation operation, the currency to use was usually (except overrides) chosen as that of the payment provider's company. This sometimes caused compatibility issues if the selected payment method did not support the company's main currency. For example, the SEPA Direct Debit payment method only supports the EUR currency. This commit allows passing a payment method when getting the validation currency so that only supported currencies can be returned. Part-of: odoo/odoo#161883
This commit is contained in:
@@ -333,7 +333,10 @@ class PaymentPortal(portal.CustomerPortal):
|
||||
)
|
||||
if is_validation: # Providers determine the amount and currency in validation operations
|
||||
amount = provider_sudo._get_validation_amount()
|
||||
currency_id = provider_sudo._get_validation_currency().id
|
||||
payment_method = request.env['payment.method'].browse(payment_method_id)
|
||||
currency_id = provider_sudo.with_context(
|
||||
validation_pm=payment_method # Will be converted to a kwarg in master.
|
||||
)._get_validation_currency().id
|
||||
|
||||
# Create the transaction
|
||||
tx_sudo = request.env['payment.transaction'].sudo().create({
|
||||
|
||||
@@ -611,9 +611,12 @@ class PaymentProvider(models.Model):
|
||||
def _get_validation_currency(self):
|
||||
""" Return the currency to use for validation operations.
|
||||
|
||||
For a provider to support tokenization, it must override this method and return the
|
||||
validation currency. If the validation amount is `0`, it is not necessary to create the
|
||||
override.
|
||||
The validation currency must be supported by both the provider and the payment method. If
|
||||
the payment method is not passed, only the provider's supported currencies are considered.
|
||||
If no suitable currency is found, the provider's company's currency is returned instead.
|
||||
|
||||
For a provider to support tokenization and specify a different validation currency, it must
|
||||
override this method and return the appropriate validation currency.
|
||||
|
||||
Note: `self.ensure_one()`
|
||||
|
||||
@@ -621,7 +624,22 @@ class PaymentProvider(models.Model):
|
||||
:rtype: recordset of `res.currency`
|
||||
"""
|
||||
self.ensure_one()
|
||||
return self.company_id.currency_id
|
||||
|
||||
# Find the validation currency at the intersection of the provider's and payment method's
|
||||
# supported currencies. An empty recordset means that all currencies are supported.
|
||||
provider_currencies = self.available_currency_ids
|
||||
pm = self.env.context.get('validation_pm')
|
||||
pm_currencies = self.env['res.currency'] if not pm else pm.supported_currency_ids
|
||||
validation_currency = None
|
||||
if provider_currencies and pm_currencies:
|
||||
validation_currency = (provider_currencies & pm_currencies)[:1]
|
||||
elif provider_currencies and not pm_currencies:
|
||||
validation_currency = provider_currencies[:1]
|
||||
elif not provider_currencies and pm_currencies:
|
||||
validation_currency = pm_currencies[:1]
|
||||
if not validation_currency: # All currencies are supported, or no suitable one was found.
|
||||
validation_currency = self.company_id.currency_id
|
||||
return validation_currency
|
||||
|
||||
def _get_redirect_form_view(self, is_validation=False):
|
||||
""" Return the view of the template used to render the redirect form.
|
||||
|
||||
@@ -226,3 +226,35 @@ class TestPaymentProvider(PaymentCommon):
|
||||
self.company.id, self.partner.id, self.amount, is_express_checkout=True
|
||||
)
|
||||
self.assertNotIn(self.provider, compatible_providers)
|
||||
|
||||
def test_validation_currency_is_supported(self):
|
||||
""" Test that only currencies supported by both the provider and the payment method can be
|
||||
used in validation operations. """
|
||||
self.provider.available_currency_ids = [Command.clear()] # Supports all currencies.
|
||||
self.payment_method.supported_currency_ids = [Command.clear()] # Supports all currencies.
|
||||
validation_currency = self.provider.with_context(
|
||||
validation_pm=self.payment_method
|
||||
)._get_validation_currency()
|
||||
self.assertEqual(validation_currency, self.provider.company_id.currency_id)
|
||||
|
||||
self.provider.available_currency_ids = [Command.set(self.currency_usd.ids)]
|
||||
self.payment_method.supported_currency_ids = [Command.clear()] # Supports all currencies.
|
||||
validation_currency = self.provider.with_context(
|
||||
validation_pm=self.payment_method
|
||||
)._get_validation_currency()
|
||||
self.assertIn(validation_currency, self.provider.available_currency_ids)
|
||||
|
||||
self.provider.available_currency_ids = [Command.clear()] # Supports all currencies.
|
||||
self.payment_method.supported_currency_ids = [Command.set(self.currency_usd.ids)]
|
||||
validation_currency = self.provider.with_context(
|
||||
validation_pm=self.payment_method
|
||||
)._get_validation_currency()
|
||||
self.assertIn(validation_currency, self.payment_method.supported_currency_ids)
|
||||
|
||||
self.provider.available_currency_ids = [Command.set(self.currency_usd.ids)]
|
||||
self.payment_method.supported_currency_ids = [Command.set(self.currency_usd.ids)]
|
||||
validation_currency = self.provider.with_context(
|
||||
validation_pm=self.payment_method
|
||||
)._get_validation_currency()
|
||||
self.assertIn(validation_currency, self.provider.available_currency_ids)
|
||||
self.assertIn(validation_currency, self.payment_method.supported_currency_ids)
|
||||
|
||||
@@ -462,7 +462,9 @@ class PaymentProvider(models.Model):
|
||||
|
||||
return stripe_utils.get_publishable_key(self.sudo())
|
||||
|
||||
def _stripe_get_inline_form_values(self, amount, currency, partner_id, is_validation, **kwargs):
|
||||
def _stripe_get_inline_form_values(
|
||||
self, amount, currency, partner_id, is_validation, payment_method_sudo=None, **kwargs
|
||||
):
|
||||
""" Return a serialized JSON of the required values to render the inline form.
|
||||
|
||||
Note: `self.ensure_one()`
|
||||
@@ -471,6 +473,8 @@ class PaymentProvider(models.Model):
|
||||
:param res.currency currency: The currency of the transaction.
|
||||
:param int partner_id: The partner of the transaction, as a `res.partner` id.
|
||||
:param bool is_validation: Whether the operation is a validation.
|
||||
:param payment.method payment_method_sudo: The sudoed payment method record to which the
|
||||
inline form belongs.
|
||||
:return: The JSON serial of the required values to render the inline form.
|
||||
:rtype: str
|
||||
"""
|
||||
@@ -479,7 +483,9 @@ class PaymentProvider(models.Model):
|
||||
if not is_validation:
|
||||
currency_name = currency and currency.name.lower()
|
||||
else:
|
||||
currency_name = self._get_validation_currency().name.lower()
|
||||
currency_name = self.with_context(
|
||||
validation_pm=payment_method_sudo # Will be converted to a kwarg in master.
|
||||
)._get_validation_currency().name.lower()
|
||||
partner = self.env['res.partner'].with_context(show_address=1).browse(partner_id).exists()
|
||||
inline_form_values = {
|
||||
'publishable_key': self._stripe_get_publishable_key(),
|
||||
|
||||
@@ -227,7 +227,9 @@ class PaymentTransaction(models.Model):
|
||||
f'{OPTION_PATH_PREFIX}[interval_count]': mandate_values['recurrence_duration'],
|
||||
})
|
||||
if self.operation == 'validation':
|
||||
currency_name = self.provider_id._get_validation_currency().name.lower()
|
||||
currency_name = self.provider_id.with_context(
|
||||
validation_pm=self.payment_method_id # Will be converted to a kwarg in master.
|
||||
)._get_validation_currency().name.lower()
|
||||
mandate_options[f'{OPTION_PATH_PREFIX}[currency]'] = currency_name
|
||||
|
||||
return mandate_options
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
currency,
|
||||
partner_id,
|
||||
mode == 'validation',
|
||||
payment_method_sudo=pm_sudo,
|
||||
sale_order_id=sale_order_id,
|
||||
)"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user