1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/payment_sepa_direct_debit/models/payment_provider.py
2024-12-10 09:04:09 +07:00

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