The payment.transaction type field allows filtering some potential payment methods down the line of a transaction being created; e.g. if a subscription product is present in a quote, the payment's type should be `form_save` to save the payment token for future use on the subscription that will be created at confirmation. The transaction that got created by sale flows did not set the type of the transaction correctly, and this value was not propagated to the rendering values of the acquirer. It now is. This is necessary as part of task 2275051, where we need to know which payment methods to enable in Stripe depending on a need for tokenization or not (since some methods are not tokenizable). closes odoo/odoo#54426 X-original-commit: 09dbbd77d83332c48f62b1c36c3b33ddbfa3a4eb Signed-off-by: Damien Bouvy (dbo) <dbo@odoo.com>
180 lines
8.2 KiB
Python
180 lines
8.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import logging
|
|
import re
|
|
|
|
from odoo import api, fields, models, _, SUPERUSER_ID
|
|
from odoo.tools import float_compare
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PaymentAcquirer(models.Model):
|
|
_inherit = 'payment.acquirer'
|
|
|
|
so_reference_type = fields.Selection(string='Communication',
|
|
selection=[
|
|
('so_name', 'Based on Document Reference'),
|
|
('partner', 'Based on Customer ID')], default='so_name',
|
|
help='You can set here the communication type that will appear on sales orders.'
|
|
'The communication will be given to the customer when they choose the payment method.')
|
|
|
|
|
|
class PaymentTransaction(models.Model):
|
|
_inherit = 'payment.transaction'
|
|
|
|
sale_order_ids = fields.Many2many('sale.order', 'sale_order_transaction_rel', 'transaction_id', 'sale_order_id',
|
|
string='Sales Orders', copy=False, readonly=True)
|
|
sale_order_ids_nbr = fields.Integer(compute='_compute_sale_order_ids_nbr', string='# of Sales Orders')
|
|
|
|
def _compute_sale_order_reference(self, order):
|
|
self.ensure_one()
|
|
if self.acquirer_id.so_reference_type == 'so_name':
|
|
return order.name
|
|
else:
|
|
# self.acquirer_id.so_reference_type == 'partner'
|
|
identification_number = order.partner_id.id
|
|
return '%s/%s' % ('CUST', str(identification_number % 97).rjust(2, '0'))
|
|
|
|
@api.depends('sale_order_ids')
|
|
def _compute_sale_order_ids_nbr(self):
|
|
for trans in self:
|
|
trans.sale_order_ids_nbr = len(trans.sale_order_ids)
|
|
|
|
def _log_payment_transaction_sent(self):
|
|
super(PaymentTransaction, self)._log_payment_transaction_sent()
|
|
for trans in self:
|
|
post_message = trans._get_payment_transaction_sent_message()
|
|
for so in trans.sale_order_ids:
|
|
so.message_post(body=post_message)
|
|
|
|
def _log_payment_transaction_received(self):
|
|
super(PaymentTransaction, self)._log_payment_transaction_received()
|
|
for trans in self.filtered(lambda t: t.provider not in ('manual', 'transfer')):
|
|
post_message = trans._get_payment_transaction_received_message()
|
|
for so in trans.sale_order_ids:
|
|
so.message_post(body=post_message)
|
|
|
|
def _set_transaction_pending(self):
|
|
# Override of '_set_transaction_pending' in the 'payment' module
|
|
# to sent the quotations automatically.
|
|
super(PaymentTransaction, self)._set_transaction_pending()
|
|
|
|
for record in self:
|
|
sales_orders = record.sale_order_ids.filtered(lambda so: so.state in ['draft', 'sent'])
|
|
sales_orders.filtered(lambda so: so.state == 'draft').with_context(tracking_disable=True).write({'state': 'sent'})
|
|
|
|
if record.acquirer_id.provider == 'transfer':
|
|
for so in record.sale_order_ids:
|
|
so.reference = record._compute_sale_order_reference(so)
|
|
# send order confirmation mail
|
|
sales_orders._send_order_confirmation_mail()
|
|
|
|
def _check_amount_and_confirm_order(self):
|
|
self.ensure_one()
|
|
for order in self.sale_order_ids.filtered(lambda so: so.state in ('draft', 'sent')):
|
|
if float_compare(self.amount, order.amount_total, 2) == 0:
|
|
order.with_context(send_email=True).action_confirm()
|
|
else:
|
|
_logger.warning(
|
|
'<%s> transaction AMOUNT MISMATCH for order %s (ID %s): expected %r, got %r',
|
|
self.acquirer_id.provider,order.name, order.id,
|
|
order.amount_total, self.amount,
|
|
)
|
|
order.message_post(
|
|
subject=_("Amount Mismatch (%s)", self.acquirer_id.provider),
|
|
body=_("The order was not confirmed despite response from the acquirer (%s): order total is %r but acquirer replied with %r.") % (
|
|
self.acquirer_id.provider,
|
|
order.amount_total,
|
|
self.amount,
|
|
)
|
|
)
|
|
|
|
def _set_transaction_authorized(self):
|
|
# Override of '_set_transaction_authorized' in the 'payment' module
|
|
# to confirm the quotations automatically.
|
|
super(PaymentTransaction, self)._set_transaction_authorized()
|
|
sales_orders = self.mapped('sale_order_ids').filtered(lambda so: so.state in ('draft', 'sent'))
|
|
for tx in self:
|
|
tx._check_amount_and_confirm_order()
|
|
|
|
# send order confirmation mail
|
|
sales_orders._send_order_confirmation_mail()
|
|
|
|
def _reconcile_after_transaction_done(self):
|
|
# Override of '_set_transaction_done' in the 'payment' module
|
|
# to confirm the quotations automatically and to generate the invoices if needed.
|
|
sales_orders = self.mapped('sale_order_ids').filtered(lambda so: so.state in ('draft', 'sent'))
|
|
for tx in self:
|
|
tx._check_amount_and_confirm_order()
|
|
# send order confirmation mail
|
|
sales_orders._send_order_confirmation_mail()
|
|
# invoice the sale orders if needed
|
|
self._invoice_sale_orders()
|
|
res = super(PaymentTransaction, self)._reconcile_after_transaction_done()
|
|
if self.env['ir.config_parameter'].sudo().get_param('sale.automatic_invoice'):
|
|
default_template = self.env['ir.config_parameter'].sudo().get_param('sale.default_email_template')
|
|
if default_template:
|
|
for trans in self.filtered(lambda t: t.sale_order_ids):
|
|
trans = trans.with_company(trans.acquirer_id.company_id).with_context(
|
|
mark_invoice_as_sent=True,
|
|
company_id=trans.acquirer_id.company_id.id,
|
|
)
|
|
for invoice in trans.invoice_ids.with_user(SUPERUSER_ID):
|
|
invoice.message_post_with_template(int(default_template), email_layout_xmlid="mail.mail_notification_paynow")
|
|
return res
|
|
|
|
def _invoice_sale_orders(self):
|
|
if self.env['ir.config_parameter'].sudo().get_param('sale.automatic_invoice'):
|
|
for trans in self.filtered(lambda t: t.sale_order_ids):
|
|
trans = trans.with_company(trans.acquirer_id.company_id)\
|
|
.with_context(company_id=trans.acquirer_id.company_id.id)
|
|
trans.sale_order_ids._force_lines_to_invoice_policy_order()
|
|
invoices = trans.sale_order_ids._create_invoices()
|
|
trans.invoice_ids = [(6, 0, invoices.ids)]
|
|
|
|
@api.model
|
|
def _compute_reference_prefix(self, values):
|
|
prefix = super(PaymentTransaction, self)._compute_reference_prefix(values)
|
|
if not prefix and values and values.get('sale_order_ids'):
|
|
sale_orders = self.new({'sale_order_ids': values['sale_order_ids']}).sale_order_ids
|
|
return ','.join(sale_orders.mapped('name'))
|
|
return prefix
|
|
|
|
def action_view_sales_orders(self):
|
|
action = {
|
|
'name': _('Sales Order(s)'),
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'sale.order',
|
|
'target': 'current',
|
|
}
|
|
sale_order_ids = self.sale_order_ids.ids
|
|
if len(sale_order_ids) == 1:
|
|
action['res_id'] = sale_order_ids[0]
|
|
action['view_mode'] = 'form'
|
|
else:
|
|
action['view_mode'] = 'tree,form'
|
|
action['domain'] = [('id', 'in', sale_order_ids)]
|
|
return action
|
|
|
|
# --------------------------------------------------
|
|
# Tools for payment
|
|
# --------------------------------------------------
|
|
|
|
def render_sale_button(self, order, submit_txt=None, render_values=None):
|
|
values = {
|
|
'partner_id': order.partner_id.id,
|
|
'type': self.type,
|
|
}
|
|
if render_values:
|
|
values.update(render_values)
|
|
# Not very elegant to do that here but no choice regarding the design.
|
|
self._log_payment_transaction_sent()
|
|
return self.acquirer_id.with_context(submit_class='btn btn-primary', submit_txt=submit_txt or _('Pay Now')).sudo().render(
|
|
self.reference,
|
|
order.amount_total,
|
|
order.pricelist_id.currency_id.id,
|
|
values=values,
|
|
)
|