140 lines
5.3 KiB
Python
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
|
|
]
|