forked from Mapan/odoo17e
212 lines
8.7 KiB
Python
212 lines
8.7 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from datetime import datetime
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import AccessError, ValidationError
|
|
|
|
from odoo.addons.payment_sepa_direct_debit import const
|
|
|
|
|
|
class PaymentProvider(models.Model):
|
|
_inherit = 'payment.provider'
|
|
|
|
custom_mode = fields.Selection(selection_add=[('sepa_direct_debit', "SEPA Direct Debit")])
|
|
|
|
#=== COMPUTE METHODS ===#
|
|
|
|
@api.depends('code')
|
|
def _compute_view_configuration_fields(self):
|
|
""" Override of payment to hide the credentials page.
|
|
|
|
:return: None
|
|
"""
|
|
super()._compute_view_configuration_fields()
|
|
self.filtered(lambda p: p.custom_mode == 'sepa_direct_debit').update({
|
|
'show_credentials_page': False,
|
|
'show_allow_tokenization': False,
|
|
'show_done_msg': False,
|
|
'show_cancel_msg': False,
|
|
})
|
|
|
|
def _compute_feature_support_fields(self):
|
|
""" Override of `payment` to enable additional features. """
|
|
super()._compute_feature_support_fields()
|
|
self.filtered(lambda p: p.custom_mode == 'sepa_direct_debit').update({
|
|
'support_tokenization': True,
|
|
})
|
|
|
|
#=== CONSTRAINT METHODS ===#
|
|
|
|
@api.constrains('state', 'journal_id')
|
|
def _check_journal_iban_is_valid(self):
|
|
""" Check that the bank account of the payment journal is a valid IBAN. """
|
|
for provider in self.filtered(
|
|
lambda p: p.custom_mode == 'sepa_direct_debit' and p.state == 'enabled'
|
|
):
|
|
if provider.journal_id.bank_account_id.acc_type != 'iban':
|
|
raise ValidationError(_("The bank account of the journal is not a valid IBAN."))
|
|
|
|
@api.constrains('state', 'company_id')
|
|
def _check_has_creditor_identifier(self):
|
|
""" Check that the company has a creditor identifier. """
|
|
for provider in self.filtered(
|
|
lambda p: p.custom_mode == 'sepa_direct_debit' and p.state == 'enabled'
|
|
):
|
|
if not provider.company_id.sdd_creditor_identifier:
|
|
raise ValidationError(_(
|
|
"Your company must have a creditor identifier in order to issue a SEPA Direct "
|
|
"Debit payment request. It can be set in Accounting settings."
|
|
))
|
|
|
|
@api.constrains('available_country_ids')
|
|
def _check_country_in_sepa_zone(self):
|
|
""" Check that all selected countries are in the SEPA zone. """
|
|
sepa_countries = self.env.ref('base.sepa_zone').country_ids
|
|
for provider in self.filtered(lambda p: p.custom_mode == 'sepa_direct_debit'):
|
|
non_sepa_countries = provider.available_country_ids - sepa_countries
|
|
if non_sepa_countries:
|
|
raise ValidationError(_(
|
|
"Restricted to countries in the SEPA zone. Forbidden countries: %s",
|
|
', '.join(non_sepa_countries.mapped('name'))
|
|
))
|
|
|
|
#=== BUSINESS METHODS ===#
|
|
|
|
@api.model
|
|
def _get_compatible_providers(self, *args, is_validation=False, **kwargs):
|
|
""" Override of `payment` to unlist SDD providers for validation flows.
|
|
|
|
Tokens are created automatically once the direct transaction is confirmed, but cannot be
|
|
created through validation flows.
|
|
"""
|
|
providers = super()._get_compatible_providers(*args, is_validation=is_validation, **kwargs)
|
|
|
|
if is_validation:
|
|
providers = providers.filtered(
|
|
lambda p: p.code != 'custom' or p.custom_mode != 'sepa_direct_debit'
|
|
)
|
|
|
|
return providers
|
|
|
|
def _get_supported_currencies(self):
|
|
""" Override of `payment` to return EUR as the only supported currency. """
|
|
supported_currencies = super()._get_supported_currencies()
|
|
if self.custom_mode == 'sepa_direct_debit':
|
|
supported_currencies = supported_currencies.filtered(lambda c: c.name == 'EUR')
|
|
return supported_currencies
|
|
|
|
def _is_tokenization_required(self, **kwargs):
|
|
""" Override of payment to hide the "Save my payment details" input in checkout forms.
|
|
|
|
:return: Whether the provider is SEPA
|
|
:rtype: bool
|
|
"""
|
|
res = super()._is_tokenization_required(**kwargs)
|
|
if len(self) != 1 or self.custom_mode != 'sepa_direct_debit':
|
|
return res
|
|
|
|
return True
|
|
|
|
def _sdd_find_or_create_mandate(self, partner_id, iban):
|
|
""" Find or create the SDD mandate verified by the given phone.
|
|
|
|
Note: self.ensure_one()
|
|
|
|
:param int partner_id: The partner making the transaction, as a `res.partner` id
|
|
:param str iban: The sanitized IBAN number of the partner's bank account
|
|
:return: The SDD mandate
|
|
:rtype: recordset of `sdd.mandate`
|
|
"""
|
|
self.ensure_one()
|
|
|
|
commercial_partner_id = self.env['res.partner'].browse(partner_id).commercial_partner_id.id
|
|
partner_bank = self._sdd_find_or_create_partner_bank(partner_id, iban)
|
|
mandate = self.env['sdd.mandate'].search([
|
|
('state', 'not in', ['closed', 'revoked']),
|
|
('start_date', '<=', datetime.now()),
|
|
'|', ('end_date', '>=', datetime.now()), ('end_date', '=', None),
|
|
('partner_id', '=', commercial_partner_id),
|
|
('partner_bank_id', '=', partner_bank.id),
|
|
('company_id', '=', self.company_id.id),
|
|
], limit=1)
|
|
if not mandate:
|
|
mandate = self.env['sdd.mandate'].create({
|
|
'partner_id': commercial_partner_id,
|
|
'partner_bank_id': partner_bank.id,
|
|
'start_date': datetime.now(),
|
|
'payment_journal_id': self.journal_id.id,
|
|
'state': 'draft',
|
|
})
|
|
return mandate
|
|
|
|
def _sdd_find_or_create_partner_bank(self, partner_id, iban):
|
|
""" Find or create the partner bank with the given iban.
|
|
|
|
Note: self.ensure_one()
|
|
|
|
:param int partner_id: The partner making the transaction, as a `res.partner` id
|
|
:param str iban: The sanitized IBAN number of the partner's bank account
|
|
:return: The partner bank
|
|
:rtype: recordset of `res.partner.bank`
|
|
"""
|
|
self.ensure_one()
|
|
|
|
ResPartnerBank = self.env['res.partner.bank']
|
|
commercial_partner_id = self.env['res.partner'].browse(partner_id).commercial_partner_id.id
|
|
partner_bank = ResPartnerBank.search([
|
|
('sanitized_acc_number', '=', iban),
|
|
('partner_id', 'child_of', commercial_partner_id),
|
|
])
|
|
if not partner_bank:
|
|
partner_bank = ResPartnerBank.create({
|
|
'acc_number': iban,
|
|
'partner_id': partner_id,
|
|
'company_id': self.company_id.id,
|
|
})
|
|
return partner_bank
|
|
|
|
def _sdd_create_token_for_mandate(self, partner, mandate):
|
|
""" Create a token linked to the mandate with the obfuscated IBAN as name and return it.
|
|
|
|
:param res.partner partner: The partner making the transaction.
|
|
:param sdd.mandate mandate: The mandate to link to the token.
|
|
:return: The created token.
|
|
:rtype: payment.token
|
|
:raise AccessError: If the partner is different than the mandate's partner.
|
|
"""
|
|
# Since we're in a sudoed env, we need to verify the partner
|
|
if mandate.partner_id != partner.commercial_partner_id:
|
|
raise AccessError("SEPA: " + _("The mandate owner and customer do not match."))
|
|
|
|
return self.env['payment.token'].create({
|
|
'provider_id': self.id,
|
|
'payment_method_id': self.payment_method_ids[:1].id,
|
|
'payment_details': mandate.partner_bank_id.acc_number,
|
|
'partner_id': partner.id,
|
|
'provider_ref': mandate.name,
|
|
'sdd_mandate_id': mandate.id,
|
|
})
|
|
|
|
def _get_provider_name(self):
|
|
""" Override of `payment` to display "Managed by SEPA" instead of "Managed by Custom" on the
|
|
payment form. """
|
|
if self.code != 'custom' or self.custom_mode != 'sepa_direct_debit':
|
|
return super()._get_provider_name()
|
|
return dict(self._fields['custom_mode']._description_selection(self.env))[self.custom_mode]
|
|
|
|
def _get_code(self):
|
|
""" Override of `payment` to trick the JS into believing the code is 'sepa_direct_debit'.
|
|
"""
|
|
res = super()._get_code()
|
|
if self.code == 'custom' and self.custom_mode == 'sepa_direct_debit':
|
|
return self.custom_mode
|
|
return res
|
|
|
|
def _get_default_payment_method_codes(self):
|
|
""" Override of `payment` to return the default payment method codes. """
|
|
default_codes = super()._get_default_payment_method_codes()
|
|
if self.custom_mode != 'sepa_direct_debit':
|
|
return default_codes
|
|
return const.DEFAULT_PAYMENT_METHOD_CODES
|