payment_transaction.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  2. import logging
  3. from werkzeug import urls
  4. from odoo import _, api, models
  5. from odoo.exceptions import ValidationError
  6. from odoo.addons.payment import utils as payment_utils
  7. from odoo.addons.payment_aps.const import PAYMENT_STATUS_MAPPING
  8. from odoo.addons.payment_aps.controllers.main import APSController
  9. _logger = logging.getLogger(__name__)
  10. class PaymentTransaction(models.Model):
  11. _inherit = 'payment.transaction'
  12. @api.model
  13. def _compute_reference(self, provider_code, prefix=None, separator='-', **kwargs):
  14. """ Override of `payment` to ensure that APS' requirements for references are satisfied.
  15. APS' requirements for transaction are as follows:
  16. - References can only be made of alphanumeric characters and/or '-' and '_'.
  17. The prefix is generated with 'tx' as default. This prevents the prefix from being
  18. generated based on document names that may contain non-allowed characters
  19. (eg: INV/2020/...).
  20. :param str provider_code: The code of the provider handling the transaction.
  21. :param str prefix: The custom prefix used to compute the full reference.
  22. :param str separator: The custom separator used to separate the prefix from the suffix.
  23. :return: The unique reference for the transaction.
  24. :rtype: str
  25. """
  26. if provider_code == 'aps':
  27. prefix = payment_utils.singularize_reference_prefix()
  28. return super()._compute_reference(provider_code, prefix=prefix, separator=separator, **kwargs)
  29. def _get_specific_rendering_values(self, processing_values):
  30. """ Override of `payment` to return APS-specific processing values.
  31. Note: self.ensure_one() from `_get_processing_values`
  32. :param dict processing_values: The generic processing values of the transaction.
  33. :return: The dict of provider-specific processing values.
  34. :rtype: dict
  35. """
  36. res = super()._get_specific_rendering_values(processing_values)
  37. if self.provider_code != 'aps':
  38. return res
  39. converted_amount = payment_utils.to_minor_currency_units(self.amount, self.currency_id)
  40. base_url = self.provider_id.get_base_url()
  41. rendering_values = {
  42. 'command': 'PURCHASE',
  43. 'access_code': self.provider_id.aps_access_code,
  44. 'merchant_identifier': self.provider_id.aps_merchant_identifier,
  45. 'merchant_reference': self.reference,
  46. 'amount': str(converted_amount),
  47. 'currency': self.currency_id.name,
  48. 'language': self.partner_lang[:2],
  49. 'customer_email': self.partner_id.email_normalized,
  50. 'return_url': urls.url_join(base_url, APSController._return_url),
  51. }
  52. rendering_values.update({
  53. 'signature': self.provider_id._aps_calculate_signature(
  54. rendering_values, incoming=False
  55. ),
  56. 'api_url': self.provider_id._aps_get_api_url(),
  57. })
  58. return rendering_values
  59. def _get_tx_from_notification_data(self, provider_code, notification_data):
  60. """ Override of `payment` to find the transaction based on APS data.
  61. :param str provider_code: The code of the provider that handled the transaction.
  62. :param dict notification_data: The notification data sent by the provider.
  63. :return: The transaction if found.
  64. :rtype: recordset of `payment.transaction`
  65. :raise ValidationError: If inconsistent data are received.
  66. :raise ValidationError: If the data match no transaction.
  67. """
  68. tx = super()._get_tx_from_notification_data(provider_code, notification_data)
  69. if provider_code != 'aps' or len(tx) == 1:
  70. return tx
  71. reference = notification_data.get('merchant_reference')
  72. if not reference:
  73. raise ValidationError(
  74. "APS: " + _("Received data with missing reference %(ref)s.", ref=reference)
  75. )
  76. tx = self.search([('reference', '=', reference), ('provider_code', '=', 'aps')])
  77. if not tx:
  78. raise ValidationError(
  79. "APS: " + _("No transaction found matching reference %s.", reference)
  80. )
  81. return tx
  82. def _process_notification_data(self, notification_data):
  83. """ Override of `payment' to process the transaction based on APS data.
  84. Note: self.ensure_one()
  85. :param dict notification_data: The notification data sent by the provider.
  86. :return: None
  87. :raise ValidationError: If inconsistent data are received.
  88. """
  89. super()._process_notification_data(notification_data)
  90. if self.provider_code != 'aps':
  91. return
  92. self.provider_reference = notification_data.get('fort_id')
  93. status = notification_data.get('status')
  94. if not status:
  95. raise ValidationError("APS: " + _("Received data with missing payment state."))
  96. if status in PAYMENT_STATUS_MAPPING['pending']:
  97. self._set_pending()
  98. elif status in PAYMENT_STATUS_MAPPING['done']:
  99. self._set_done()
  100. else: # Classify unsupported payment state as `error` tx state.
  101. status_description = notification_data.get('response_message')
  102. _logger.info(
  103. "Received data with invalid payment status (%(status)s) and reason '%(reason)s' "
  104. "for transaction with reference %(ref)s",
  105. {'status': status, 'reason': status_description, 'ref': self.reference},
  106. )
  107. self._set_error("APS: " + _(
  108. "Received invalid transaction status %(status)s and reason '%(reason)s'.",
  109. status=status, reason=status_description
  110. ))