123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- # 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
- ]
|