1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
|
# 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
)
|