217 lines
7.9 KiB
Python
217 lines
7.9 KiB
Python
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
|
|
||
|
import logging
|
||
|
|
||
|
from odoo import _, fields, models
|
||
|
from odoo.exceptions import UserError, ValidationError
|
||
|
|
||
|
_logger = logging.getLogger(__name__)
|
||
|
|
||
|
|
||
|
class PaymentTransaction(models.Model):
|
||
|
_inherit = 'payment.transaction'
|
||
|
|
||
|
capture_manually = fields.Boolean(related='provider_id.capture_manually')
|
||
|
|
||
|
#=== ACTION METHODS ===#
|
||
|
|
||
|
def action_demo_set_done(self):
|
||
|
""" Set the state of the demo transaction to 'done'.
|
||
|
|
||
|
Note: self.ensure_one()
|
||
|
|
||
|
:return: None
|
||
|
"""
|
||
|
self.ensure_one()
|
||
|
if self.provider_code != 'demo':
|
||
|
return
|
||
|
|
||
|
notification_data = {'reference': self.reference, 'simulated_state': 'done'}
|
||
|
self._handle_notification_data('demo', notification_data)
|
||
|
|
||
|
def action_demo_set_canceled(self):
|
||
|
""" Set the state of the demo transaction to 'cancel'.
|
||
|
|
||
|
Note: self.ensure_one()
|
||
|
|
||
|
:return: None
|
||
|
"""
|
||
|
self.ensure_one()
|
||
|
if self.provider_code != 'demo':
|
||
|
return
|
||
|
|
||
|
notification_data = {'reference': self.reference, 'simulated_state': 'cancel'}
|
||
|
self._handle_notification_data('demo', notification_data)
|
||
|
|
||
|
def action_demo_set_error(self):
|
||
|
""" Set the state of the demo transaction to 'error'.
|
||
|
|
||
|
Note: self.ensure_one()
|
||
|
|
||
|
:return: None
|
||
|
"""
|
||
|
self.ensure_one()
|
||
|
if self.provider_code != 'demo':
|
||
|
return
|
||
|
|
||
|
notification_data = {'reference': self.reference, 'simulated_state': 'error'}
|
||
|
self._handle_notification_data('demo', notification_data)
|
||
|
|
||
|
#=== BUSINESS METHODS ===#
|
||
|
|
||
|
def _send_payment_request(self):
|
||
|
""" Override of payment to simulate a payment request.
|
||
|
|
||
|
Note: self.ensure_one()
|
||
|
|
||
|
:return: None
|
||
|
"""
|
||
|
super()._send_payment_request()
|
||
|
if self.provider_code != 'demo':
|
||
|
return
|
||
|
|
||
|
if not self.token_id:
|
||
|
raise UserError("Demo: " + _("The transaction is not linked to a token."))
|
||
|
|
||
|
simulated_state = self.token_id.demo_simulated_state
|
||
|
notification_data = {'reference': self.reference, 'simulated_state': simulated_state}
|
||
|
self._handle_notification_data('demo', notification_data)
|
||
|
|
||
|
def _send_refund_request(self, **kwargs):
|
||
|
""" Override of payment to simulate a refund.
|
||
|
|
||
|
Note: self.ensure_one()
|
||
|
|
||
|
:param dict kwargs: The keyword arguments.
|
||
|
:return: The refund transaction created to process the refund request.
|
||
|
:rtype: recordset of `payment.transaction`
|
||
|
"""
|
||
|
refund_tx = super()._send_refund_request(**kwargs)
|
||
|
if self.provider_code != 'demo':
|
||
|
return refund_tx
|
||
|
|
||
|
notification_data = {'reference': refund_tx.reference, 'simulated_state': 'done'}
|
||
|
refund_tx._handle_notification_data('demo', notification_data)
|
||
|
|
||
|
return refund_tx
|
||
|
|
||
|
def _send_capture_request(self, amount_to_capture=None):
|
||
|
""" Override of `payment` to simulate a capture request. """
|
||
|
child_capture_tx = super()._send_capture_request(amount_to_capture=amount_to_capture)
|
||
|
if self.provider_code != 'demo':
|
||
|
return child_capture_tx
|
||
|
|
||
|
tx = child_capture_tx or self
|
||
|
notification_data = {
|
||
|
'reference': tx.reference,
|
||
|
'simulated_state': 'done',
|
||
|
'manual_capture': True, # Distinguish manual captures from regular one-step captures.
|
||
|
}
|
||
|
tx._handle_notification_data('demo', notification_data)
|
||
|
|
||
|
return child_capture_tx
|
||
|
|
||
|
def _send_void_request(self, amount_to_void=None):
|
||
|
""" Override of `payment` to simulate a void request. """
|
||
|
child_void_tx = super()._send_void_request(amount_to_void=amount_to_void)
|
||
|
if self.provider_code != 'demo':
|
||
|
return child_void_tx
|
||
|
|
||
|
tx = child_void_tx or self
|
||
|
notification_data = {'reference': tx.reference, 'simulated_state': 'cancel'}
|
||
|
tx._handle_notification_data('demo', notification_data)
|
||
|
|
||
|
return child_void_tx
|
||
|
|
||
|
def _get_tx_from_notification_data(self, provider_code, notification_data):
|
||
|
""" Override of payment to find the transaction based on dummy data.
|
||
|
|
||
|
:param str provider_code: The code of the provider that handled the transaction
|
||
|
:param dict notification_data: The dummy notification data
|
||
|
:return: The transaction if found
|
||
|
:rtype: recordset of `payment.transaction`
|
||
|
:raise: ValidationError if the data match no transaction
|
||
|
"""
|
||
|
tx = super()._get_tx_from_notification_data(provider_code, notification_data)
|
||
|
if provider_code != 'demo' or len(tx) == 1:
|
||
|
return tx
|
||
|
|
||
|
reference = notification_data.get('reference')
|
||
|
tx = self.search([('reference', '=', reference), ('provider_code', '=', 'demo')])
|
||
|
if not tx:
|
||
|
raise ValidationError(
|
||
|
"Demo: " + _("No transaction found matching reference %s.", reference)
|
||
|
)
|
||
|
return tx
|
||
|
|
||
|
def _process_notification_data(self, notification_data):
|
||
|
""" Override of payment to process the transaction based on dummy data.
|
||
|
|
||
|
Note: self.ensure_one()
|
||
|
|
||
|
:param dict notification_data: The dummy notification data
|
||
|
:return: None
|
||
|
:raise: ValidationError if inconsistent data were received
|
||
|
"""
|
||
|
super()._process_notification_data(notification_data)
|
||
|
if self.provider_code != 'demo':
|
||
|
return
|
||
|
|
||
|
# Update the provider reference.
|
||
|
self.provider_reference = f'demo-{self.reference}'
|
||
|
|
||
|
# Create the token.
|
||
|
if self.tokenize:
|
||
|
# The reasons why we immediately tokenize the transaction regardless of the state rather
|
||
|
# than waiting for the payment method to be validated ('authorized' or 'done') like the
|
||
|
# other payment providers do are:
|
||
|
# - To save the simulated state and payment details on the token while we have them.
|
||
|
# - To allow customers to create tokens whose transactions will always end up in the
|
||
|
# said simulated state.
|
||
|
self._demo_tokenize_from_notification_data(notification_data)
|
||
|
|
||
|
# Update the payment state.
|
||
|
state = notification_data['simulated_state']
|
||
|
if state == 'pending':
|
||
|
self._set_pending()
|
||
|
elif state == 'done':
|
||
|
if self.capture_manually and not notification_data.get('manual_capture'):
|
||
|
self._set_authorized()
|
||
|
else:
|
||
|
self._set_done()
|
||
|
# Immediately post-process the transaction if it is a refund, as the post-processing
|
||
|
# will not be triggered by a customer browsing the transaction from the portal.
|
||
|
if self.operation == 'refund':
|
||
|
self.env.ref('payment.cron_post_process_payment_tx')._trigger()
|
||
|
elif state == 'cancel':
|
||
|
self._set_canceled()
|
||
|
else: # Simulate an error state.
|
||
|
self._set_error(_("You selected the following demo payment status: %s", state))
|
||
|
|
||
|
def _demo_tokenize_from_notification_data(self, notification_data):
|
||
|
""" Create a new token based on the notification data.
|
||
|
|
||
|
Note: self.ensure_one()
|
||
|
|
||
|
:param dict notification_data: The fake notification data to tokenize from.
|
||
|
:return: None
|
||
|
"""
|
||
|
self.ensure_one()
|
||
|
|
||
|
state = notification_data['simulated_state']
|
||
|
token = self.env['payment.token'].create({
|
||
|
'provider_id': self.provider_id.id,
|
||
|
'payment_method_id': self.payment_method_id.id,
|
||
|
'payment_details': notification_data['payment_details'],
|
||
|
'partner_id': self.partner_id.id,
|
||
|
'provider_ref': 'fake provider reference',
|
||
|
'demo_simulated_state': state,
|
||
|
})
|
||
|
self.write({
|
||
|
'token_id': token,
|
||
|
'tokenize': False,
|
||
|
})
|
||
|
_logger.info(
|
||
|
"Created token with id %s for partner with id %s.", token.id, self.partner_id.id
|
||
|
)
|