Files
odoo_source/addons/sale/models/payment.py
T
Damien Bouvy c547f12efb [FIX] sale: set and propagate payment type in payment flow
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>
2020-07-14 09:32:57 +00:00

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,
)