payment_demo/models/payment_transaction.py

217 lines
7.9 KiB
Python
Raw Normal View History

# 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
)