[IMP] payment: post-process transactions immediately

Instead of monitoring a list of transactions to post-process in the
session, we now track only the last processed transaction, thus allowing
to drop the 10 min delay of the post-processing cron.

task-3125913

Part-of: odoo/odoo#119473
Co-authored-by: mano-odoo <mano@odoo.com>
This commit is contained in:
Anita (anko)
2023-09-19 16:37:06 +00:00
committed by Antoine Vandevenne (anv)
co-authored by mano-odoo
parent 23faf6f7ef
commit cf52e381bd
14 changed files with 342 additions and 455 deletions
+37 -88
View File
@@ -1,11 +1,10 @@
# 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 import http
from odoo.http import request
_logger = logging.getLogger(__name__)
@@ -22,7 +21,7 @@ class PaymentPostProcessing(http.Controller):
their post-processing.
"""
MONITORED_TX_IDS_KEY = '__payment_monitored_tx_ids__'
MONITORED_TX_ID_KEY = '__payment_monitored_tx_id__'
@http.route('/payment/status', type='http', auth='public', website=True, sitemap=False)
def display_status(self, **kwargs):
@@ -36,104 +35,54 @@ class PaymentPostProcessing(http.Controller):
@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.
""" Fetch the transaction to display on the status page and finalize its post-processing.
:return: The post-processing values of the transactions
:return: The post-processing values of the transaction.
: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',
}
# Retrieve the last user's transaction from the session.
monitored_tx = request.env['payment.transaction'].sudo().browse(
self.get_monitored_transaction_id()
).exists()
if not monitored_tx: # The session might have expired, or the tx has never existed.
raise Exception('tx_not_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(),
})
# Finalize the post-processing of the transaction before redirecting the user to the landing
# route and its document.
if monitored_tx.state == 'done' and not monitored_tx.is_post_processed:
try:
monitored_tx._finalize_post_processing()
except psycopg2.OperationalError: # The database cursor could not be committed.
request.env.cr.rollback() # Rollback and try later.
raise Exception('retry')
except Exception as e:
request.env.cr.rollback()
_logger.exception(
"Encountered an error while post-processing transaction with id %s:\n%s",
monitored_tx.id, e
)
raise
# 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,
}
# Return the post-processing values to display the transaction summary to the customer.
return monitored_tx._get_post_processing_values()
@classmethod
def monitor_transactions(cls, transactions):
""" Add the ids of the provided transactions to the list of monitored transaction ids.
def monitor_transaction(cls, transaction):
""" Make the provided transaction id monitored.
:param recordset transactions: The transactions to monitor, as a `payment.transaction`
recordset
:param payment.transaction transaction: The transaction to monitor.
: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)
)
request.session[cls.MONITORED_TX_ID_KEY] = transaction.id
@classmethod
def get_monitored_transaction_ids(cls):
""" Return the ids of transactions being monitored.
def get_monitored_transaction_id(cls):
""" Return the id of transaction 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.
Only the id and not the recordset itself is returned to allow the caller browsing the
recordset with sudo privileges, and using the id in a custom query.
:return: The ids of transactions being monitored
:return: The id 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
]
return request.session.get(cls.MONITORED_TX_ID_KEY)