post_processing.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  2. import logging
  3. from datetime import timedelta
  4. import psycopg2
  5. from odoo import fields, http
  6. from odoo.http import request
  7. _logger = logging.getLogger(__name__)
  8. class PaymentPostProcessing(http.Controller):
  9. """
  10. This controller is responsible for the monitoring and finalization of the post-processing of
  11. transactions.
  12. It exposes the route `/payment/status`: All payment flows must go through this route at some
  13. point to allow the user checking on the transactions' status, and to trigger the finalization of
  14. their post-processing.
  15. """
  16. MONITORED_TX_IDS_KEY = '__payment_monitored_tx_ids__'
  17. @http.route('/payment/status', type='http', auth='public', website=True, sitemap=False)
  18. def display_status(self, **kwargs):
  19. """ Display the payment status page.
  20. :param dict kwargs: Optional data. This parameter is not used here
  21. :return: The rendered status page
  22. :rtype: str
  23. """
  24. return request.render('payment.payment_status')
  25. @http.route('/payment/status/poll', type='json', auth='public')
  26. def poll_status(self, **_kwargs):
  27. """ Fetch the transactions to display on the status page and finalize their post-processing.
  28. :return: The post-processing values of the transactions
  29. :rtype: dict
  30. """
  31. # Retrieve recent user's transactions from the session
  32. limit_date = fields.Datetime.now() - timedelta(days=1)
  33. monitored_txs = request.env['payment.transaction'].sudo().search([
  34. ('id', 'in', self.get_monitored_transaction_ids()),
  35. ('last_state_change', '>=', limit_date)
  36. ])
  37. if not monitored_txs: # The transaction was not correctly created
  38. return {
  39. 'success': False,
  40. 'error': 'no_tx_found',
  41. }
  42. # Build the list of display values with the display message and post-processing values
  43. display_values_list = []
  44. for tx in monitored_txs:
  45. display_message = None
  46. if tx.state == 'pending':
  47. display_message = tx.provider_id.pending_msg
  48. elif tx.state == 'done':
  49. display_message = tx.provider_id.done_msg
  50. elif tx.state == 'cancel':
  51. display_message = tx.provider_id.cancel_msg
  52. display_values_list.append({
  53. 'display_message': display_message,
  54. **tx._get_post_processing_values(),
  55. })
  56. # Stop monitoring already post-processed transactions
  57. post_processed_txs = monitored_txs.filtered('is_post_processed')
  58. self.remove_transactions(post_processed_txs)
  59. # Finalize post-processing of transactions before displaying them to the user
  60. txs_to_post_process = (monitored_txs - post_processed_txs).filtered(
  61. lambda t: t.state == 'done'
  62. )
  63. success, error = True, None
  64. try:
  65. txs_to_post_process._finalize_post_processing()
  66. except psycopg2.OperationalError: # A collision of accounting sequences occurred
  67. request.env.cr.rollback() # Rollback and try later
  68. success = False
  69. error = 'tx_process_retry'
  70. except Exception as e:
  71. request.env.cr.rollback()
  72. success = False
  73. error = str(e)
  74. _logger.exception(
  75. "encountered an error while post-processing transactions with ids %s:\n%s",
  76. ', '.join([str(tx_id) for tx_id in txs_to_post_process.ids]), e
  77. )
  78. return {
  79. 'success': success,
  80. 'error': error,
  81. 'display_values_list': display_values_list,
  82. }
  83. @classmethod
  84. def monitor_transactions(cls, transactions):
  85. """ Add the ids of the provided transactions to the list of monitored transaction ids.
  86. :param recordset transactions: The transactions to monitor, as a `payment.transaction`
  87. recordset
  88. :return: None
  89. """
  90. if transactions:
  91. monitored_tx_ids = request.session.get(cls.MONITORED_TX_IDS_KEY, [])
  92. request.session[cls.MONITORED_TX_IDS_KEY] = list(
  93. set(monitored_tx_ids).union(transactions.ids)
  94. )
  95. @classmethod
  96. def get_monitored_transaction_ids(cls):
  97. """ Return the ids of transactions being monitored.
  98. Only the ids and not the recordset itself is returned to allow the caller browsing the
  99. recordset with sudo privileges, and using the ids in a custom query.
  100. :return: The ids of transactions being monitored
  101. :rtype: list
  102. """
  103. return request.session.get(cls.MONITORED_TX_IDS_KEY, [])
  104. @classmethod
  105. def remove_transactions(cls, transactions):
  106. """ Remove the ids of the provided transactions from the list of monitored transaction ids.
  107. :param recordset transactions: The transactions to remove, as a `payment.transaction`
  108. recordset
  109. :return: None
  110. """
  111. if transactions:
  112. monitored_tx_ids = request.session.get(cls.MONITORED_TX_IDS_KEY, [])
  113. request.session[cls.MONITORED_TX_IDS_KEY] = [
  114. tx_id for tx_id in monitored_tx_ids if tx_id not in transactions.ids
  115. ]