Odoo18-Base/addons/payment/controllers/post_processing.py
2025-03-10 11:12:23 +07:00

140 lines
5.3 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from datetime import timedelta
import psycopg2
from odoo import fields, http
from odoo.http import request
_logger = logging.getLogger(__name__)
class PaymentPostProcessing(http.Controller):
"""
This controller is responsible for the monitoring and finalization of the post-processing of
transactions.
It exposes the route `/payment/status`: All payment flows must go through this route at some
point to allow the user checking on the transactions' status, and to trigger the finalization of
their post-processing.
"""
MONITORED_TX_IDS_KEY = '__payment_monitored_tx_ids__'
@http.route('/payment/status', type='http', auth='public', website=True, sitemap=False)
def display_status(self, **kwargs):
""" Display the payment status page.
:param dict kwargs: Optional data. This parameter is not used here
:return: The rendered status page
:rtype: str
"""
return request.render('payment.payment_status')
@http.route('/payment/status/poll', type='json', auth='public')
def poll_status(self, **_kwargs):
""" Fetch the transactions to display on the status page and finalize their post-processing.
:return: The post-processing values of the transactions
:rtype: dict
"""
# Retrieve recent user's transactions from the session
limit_date = fields.Datetime.now() - timedelta(days=1)
monitored_txs = request.env['payment.transaction'].sudo().search([
('id', 'in', self.get_monitored_transaction_ids()),
('last_state_change', '>=', limit_date)
])
if not monitored_txs: # The transaction was not correctly created
return {
'success': False,
'error': 'no_tx_found',
}
# Build the list of display values with the display message and post-processing values
display_values_list = []
for tx in monitored_txs:
display_message = None
if tx.state == 'pending':
display_message = tx.provider_id.pending_msg
elif tx.state == 'done':
display_message = tx.provider_id.done_msg
elif tx.state == 'cancel':
display_message = tx.provider_id.cancel_msg
display_values_list.append({
'display_message': display_message,
**tx._get_post_processing_values(),
})
# Stop monitoring already post-processed transactions
post_processed_txs = monitored_txs.filtered('is_post_processed')
self.remove_transactions(post_processed_txs)
# Finalize post-processing of transactions before displaying them to the user
txs_to_post_process = (monitored_txs - post_processed_txs).filtered(
lambda t: t.state == 'done'
)
success, error = True, None
try:
txs_to_post_process._finalize_post_processing()
except psycopg2.OperationalError: # A collision of accounting sequences occurred
request.env.cr.rollback() # Rollback and try later
success = False
error = 'tx_process_retry'
except Exception as e:
request.env.cr.rollback()
success = False
error = str(e)
_logger.exception(
"encountered an error while post-processing transactions with ids %s:\n%s",
', '.join([str(tx_id) for tx_id in txs_to_post_process.ids]), e
)
return {
'success': success,
'error': error,
'display_values_list': display_values_list,
}
@classmethod
def monitor_transactions(cls, transactions):
""" Add the ids of the provided transactions to the list of monitored transaction ids.
:param recordset transactions: The transactions to monitor, as a `payment.transaction`
recordset
:return: None
"""
if transactions:
monitored_tx_ids = request.session.get(cls.MONITORED_TX_IDS_KEY, [])
request.session[cls.MONITORED_TX_IDS_KEY] = list(
set(monitored_tx_ids).union(transactions.ids)
)
@classmethod
def get_monitored_transaction_ids(cls):
""" Return the ids of transactions being monitored.
Only the ids and not the recordset itself is returned to allow the caller browsing the
recordset with sudo privileges, and using the ids in a custom query.
:return: The ids of transactions being monitored
:rtype: list
"""
return request.session.get(cls.MONITORED_TX_IDS_KEY, [])
@classmethod
def remove_transactions(cls, transactions):
""" Remove the ids of the provided transactions from the list of monitored transaction ids.
:param recordset transactions: The transactions to remove, as a `payment.transaction`
recordset
:return: None
"""
if transactions:
monitored_tx_ids = request.session.get(cls.MONITORED_TX_IDS_KEY, [])
request.session[cls.MONITORED_TX_IDS_KEY] = [
tx_id for tx_id in monitored_tx_ids if tx_id not in transactions.ids
]