You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

170 lines
5.7 KiB

  1. # Copyright 2017 LasLabs Inc.
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  3. import logging
  4. from datetime import datetime, timedelta
  5. from odoo import api, fields, models, _
  6. _logger = logging.getLogger(__name__)
  7. class Contract(models.Model):
  8. _inherit = 'contract.contract'
  9. payment_token_id = fields.Many2one(
  10. string='Payment Token',
  11. comodel_name='payment.token',
  12. domain="[('partner_id', '=', partner_id)]",
  13. context="{'default_partner_id': partner_id}",
  14. help='This is the payment token that will be used to automatically '
  15. 'reconcile debts against this account. If none is set, the '
  16. 'bill to partner\'s default token will be used.',
  17. )
  18. @api.multi
  19. @api.onchange('partner_id')
  20. def _onchange_partner_id_payment_token(self):
  21. """ Clear the payment token when the partner is changed. """
  22. self.payment_token_id = self.env['payment.token']
  23. @api.model
  24. def cron_retry_auto_pay(self):
  25. """ Retry automatic payments for appropriate invoices. """
  26. invoice_lines = self.env['account.invoice.line'].search([
  27. ('invoice_id.state', '=', 'open'),
  28. ('invoice_id.auto_pay_attempts', '>', 0),
  29. ('contract_line_id.contract_id.is_auto_pay', '=', True),
  30. ])
  31. now = datetime.now()
  32. for invoice_line in invoice_lines:
  33. contract = invoice_line.contract_line_id.contract_id
  34. invoice = invoice_line.invoice_id
  35. fail_time = invoice.auto_pay_failed
  36. retry_delta = timedelta(hours=contract.auto_pay_retry_hours)
  37. retry_time = fail_time + retry_delta
  38. if retry_time < now:
  39. contract._do_auto_pay(invoice)
  40. @api.multi
  41. def _recurring_create_invoice(self):
  42. """ If automatic payment is enabled, perform auto pay actions. """
  43. invoice = super(Contract, self)._recurring_create_invoice()
  44. if not self.is_auto_pay:
  45. return invoice
  46. self._do_auto_pay(invoice)
  47. return invoice
  48. @api.multi
  49. def _do_auto_pay(self, invoice):
  50. """ Perform all automatic payment operations on open invoices. """
  51. self.ensure_one()
  52. invoice.ensure_one()
  53. invoice.action_invoice_open()
  54. self._send_invoice_message(invoice)
  55. self._pay_invoice(invoice)
  56. @api.multi
  57. def _pay_invoice(self, invoice):
  58. """ Pay the invoice using the account or partner token. """
  59. if invoice.state != 'open':
  60. _logger.info('Cannot pay an invoice that is not in open state.')
  61. return
  62. if not invoice.residual:
  63. _logger.debug('Cannot pay an invoice with no balance.')
  64. return
  65. token = self.payment_token_id or self.partner_id.payment_token_id
  66. if not token:
  67. _logger.debug(
  68. 'Cannot pay an invoice without defining a payment token',
  69. )
  70. return
  71. transaction = self.env['payment.transaction'].create(
  72. self._get_tx_vals(invoice, token),
  73. )
  74. valid_states = ['authorized', 'done']
  75. try:
  76. result = transaction.s2s_do_transaction()
  77. if not result or transaction.state not in valid_states:
  78. _logger.debug(
  79. 'Payment transaction failed (%s)',
  80. transaction.state_message,
  81. )
  82. else:
  83. # Success
  84. return True
  85. except Exception:
  86. _logger.exception(
  87. 'Payment transaction (%s) generated a gateway error.',
  88. transaction.id,
  89. )
  90. transaction.state = 'error'
  91. invoice.write({
  92. 'auto_pay_attempts': invoice.auto_pay_attempts + 1,
  93. 'auto_pay_failed': fields.Datetime.now(),
  94. })
  95. if invoice.auto_pay_attempts >= self.auto_pay_retries:
  96. template = self.pay_fail_mail_template_id
  97. self.write({
  98. 'is_auto_pay': False,
  99. 'payment_token_id': False,
  100. })
  101. if token == self.partner_id.payment_token_id:
  102. self.partner_id.payment_token_id = False
  103. else:
  104. template = self.pay_retry_mail_template_id
  105. if template:
  106. template.send_mail(invoice.id)
  107. return
  108. @api.multi
  109. def _get_tx_vals(self, invoice, token):
  110. """ Return values for create of payment.transaction for invoice."""
  111. amount_due = invoice.residual
  112. partner = token.partner_id
  113. reference = self.env['payment.transaction']._compute_reference()
  114. return {
  115. 'reference': '%s' % reference,
  116. 'acquirer_id': token.acquirer_id.id,
  117. 'payment_token_id': token.id,
  118. 'amount': amount_due,
  119. 'state': 'draft',
  120. 'currency_id': invoice.currency_id.id,
  121. 'partner_id': partner.id,
  122. 'partner_country_id': partner.country_id.id,
  123. 'partner_city': partner.city,
  124. 'partner_zip': partner.zip,
  125. 'partner_email': partner.email,
  126. }
  127. @api.multi
  128. def _send_invoice_message(self, invoice):
  129. """ Send the appropriate emails for the invoices if needed. """
  130. if invoice.sent:
  131. return
  132. if not self.invoice_mail_template_id:
  133. return
  134. _logger.info('Sending invoice %s, %s (template %s)',
  135. invoice, invoice.number, self.invoice_mail_template_id)
  136. mail_id = self.invoice_mail_template_id.send_mail(invoice.id)
  137. invoice.with_context(mail_post_autofollow=True)
  138. invoice.sent = True
  139. invoice.message_post(body=_("Invoice sent"))
  140. return self.env['mail.mail'].browse(mail_id)