1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
|
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import fields, models, _
from odoo.tools import float_compare
_logger = logging.getLogger(__name__)
class PaymentTransaction(models.Model):
_inherit = 'payment.transaction'
account_invoice_id = fields.Many2one('account.invoice', string='Invoice')
def form_feedback(self, data, acquirer_name):
""" Override to confirm the invoice, if defined, and if the transaction is done. """
tx = None
res = super(PaymentTransaction, self).form_feedback(data, acquirer_name)
# fetch the tx
tx_find_method_name = '_%s_form_get_tx_from_data' % acquirer_name
if hasattr(self, tx_find_method_name):
tx = getattr(self, tx_find_method_name)(data)
if tx and tx.account_invoice_id:
_logger.info(
'<%s> transaction <%s> processing form feedback for invoice <%s>: tx ref:%s, tx amount: %s',
acquirer_name, tx.id, tx.account_invoice_id.id, tx.reference, tx.amount)
tx._confirm_invoice()
return res
def confirm_invoice_token(self):
""" Confirm a transaction token and call SO confirmation if it is a success.
:return: True if success; error string otherwise """
self.ensure_one()
if self.payment_token_id and self.partner_id == self.account_invoice_id.partner_id:
try:
s2s_result = self.s2s_do_transaction()
except Exception as e:
_logger.warning(
_("<%s> transaction (%s) failed : <%s>") %
(self.acquirer_id.provider, self.id, str(e)))
return 'pay_invoice_tx_fail'
valid_state = 'authorized' if self.acquirer_id.capture_manually else 'done'
if not s2s_result or self.state != valid_state:
_logger.warning(
_("<%s> transaction (%s) invalid state : %s") %
(self.acquirer_id.provider, self.id, self.state_mesage))
return 'pay_invoice_tx_state'
try:
# Auto-confirm SO if necessary
return self._confirm_invoice()
except Exception as e:
_logger.warning(
_("<%s> transaction (%s) invoice confirmation failed : <%s>") %
(self.acquirer_id.provider, self.id, str(e)))
return 'pay_invoice_tx_confirm'
return 'pay_invoice_tx_token'
def _confirm_invoice(self):
""" Check tx state, confirm and pay potential invoice """
self.ensure_one()
# check tx state, confirm the potential SO
if self.account_invoice_id.state != 'open':
_logger.warning('<%s> transaction STATE INCORRECT for invoice %s (ID %s, state %s)', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id, self.account_invoice_id.state)
return 'pay_invoice_invalid_doc_state'
if not float_compare(self.amount, self.account_invoice_id.amount_total, 2) == 0:
_logger.warning(
'<%s> transaction AMOUNT MISMATCH for invoice %s (ID %s): expected %r, got %r',
self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id,
self.account_invoice_id.amount_total, self.amount,
)
self.account_invoice_id.message_post(
subject=_("Amount Mismatch (%s)") % self.acquirer_id.provider,
body=_("The invoice was not confirmed despite response from the acquirer (%s): invoice amount is %r but acquirer replied with %r.") % (
self.acquirer_id.provider,
self.account_invoice_id.amount_total,
self.amount,
)
)
return 'pay_invoice_tx_amount'
if self.state == 'authorized' and self.acquirer_id.capture_manually:
_logger.info('<%s> transaction authorized, nothing to do with invoice %s (ID %s)', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id)
elif self.state == 'done':
_logger.info('<%s> transaction completed, paying invoice %s (ID %s)', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id)
self._pay_invoice()
else:
_logger.warning('<%s> transaction MISMATCH for invoice %s (ID %s)', self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id)
return 'pay_invoice_tx_state'
return True
def _pay_invoice(self):
self.ensure_one()
# force company to ensure journals/accounts etc. are correct
# company_id needed for default_get on account.journal
# force_company needed for company_dependent fields
ctx_company = {'company_id': self.account_invoice_id.company_id.id,
'force_company': self.account_invoice_id.company_id.id}
invoice = self.account_invoice_id.with_context(**ctx_company)
if not self.acquirer_id.journal_id:
default_journal = self.env['account.journal'].search([('type', '=', 'bank')], limit=1)
if not default_journal:
_logger.warning('<%s> transaction completed, could not auto-generate payment for invoice %s (ID %s) (no journal set on acquirer)',
self.acquirer_id.provider, self.account_invoice_id.number, self.account_invoice_id.id)
return False
self.acquirer_id.journal_id = default_journal
invoice.pay_and_reconcile(self.acquirer_id.journal_id, pay_amount=invoice.amount_total)
invoice.payment_ids.write({'payment_transaction_id': self.ids[0]})
_logger.info('<%s> transaction <%s> completed, reconciled invoice %s (ID %s))',
self.acquirer_id.provider, self.ids[0], invoice.number, invoice.id)
return True
def render_invoice_button(self, invoice, return_url, submit_txt=None, render_values=None):
values = {
'return_url': return_url,
'partner_id': invoice.partner_id.id,
}
if render_values:
values.update(render_values)
return self.acquirer_id.with_context(submit_class='btn btn-primary', submit_txt=submit_txt or _('Pay Now')).sudo().render(
self.reference,
invoice.amount_total,
invoice.currency_id.id,
values=values,
)
def _check_or_create_invoice_tx(self, invoice, acquirer, payment_token=None, tx_type='form', add_tx_values=None):
tx = self
if not tx:
tx = self.search([('reference', '=', invoice.number)], limit=1)
if tx and tx.state in ['error', 'cancel']: # filter incorrect states
tx = False
if (tx and acquirer and tx.acquirer_id != acquirer) or (tx and tx.account_invoice_id != invoice): # filter unmatching
tx = False
if tx and tx.payment_token_id and payment_token and payment_token != tx.payment_token_id: # new or distinct token
tx = False
# still draft tx, no more info -> create a new one
if tx and tx.state == 'draft':
tx = False
if not tx:
tx_values = {
'acquirer_id': acquirer.id,
'type': tx_type,
'amount': invoice.amount_total,
'currency_id': invoice.currency_id.id,
'partner_id': invoice.partner_id.id,
'partner_country_id': invoice.partner_id.country_id.id,
'reference': self._get_next_reference(invoice.number, acquirer=acquirer),
'account_invoice_id': invoice.id,
}
if add_tx_values:
tx_values.update(add_tx_values)
if payment_token and payment_token.sudo().partner_id == invoice.partner_id:
tx_values['payment_token_id'] = payment_token.id
tx = self.create(tx_values)
# update invoice
invoice.write({
'payment_tx_id': tx.id,
})
return tx
def _post_process_after_done(self, **kwargs):
# set invoice id in payment transaction when payment being done from sale order
res = super(PaymentTransaction, self)._post_process_after_done()
if kwargs.get('invoice_id'):
self.account_invoice_id = kwargs['invoice_id']
return res
|