You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

254 lines
9.0 KiB

# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Afra K (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
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_credit_pay_set_done(self):
""" Set the state of the credit_pay transaction to 'done'.
Note: self.ensure_one()
:return: None
"""
self.ensure_one()
if self.provider_code != 'credit_pay':
return
notification_data = {'reference': self.reference,
'simulated_state': 'done'}
self._handle_notification_data('credit_pay', notification_data)
def action_credit_pay_set_canceled(self):
""" Set the state of the credit_pay transaction to 'cancel'.
Note: self.ensure_one()
:return: None
"""
self.ensure_one()
if self.provider_code != 'credit_pay':
return
notification_data = {'reference': self.reference,
'simulated_state': 'cancel'}
self._handle_notification_data('credit_pay', notification_data)
def action_credit_pay_set_error(self):
""" Set the state of the credit_pay transaction to 'error'.
Note: self.ensure_one()
:return: None
"""
self.ensure_one()
if self.provider_code != 'credit_pay':
return
notification_data = {'reference': self.reference,
'simulated_state': 'error'}
self._handle_notification_data('credit_pay', 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 != 'credit_pay':
return
if not self.token_id:
raise UserError(
"Demo: " + _("The transaction is not linked to a token."))
simulated_state = self.token_id.credit_pay_simulated_state
notification_data = {'reference': self.reference,
'simulated_state': simulated_state}
self._handle_notification_data('credit_pay', 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 != 'credit_pay':
return refund_tx
notification_data = {'reference': refund_tx.reference,
'simulated_state': 'done'}
refund_tx._handle_notification_data('credit_pay', notification_data)
return refund_tx
def _send_capture_request(self):
""" Override of payment to simulate a capture request.
Note: self.ensure_one()
:return: None
"""
super()._send_capture_request()
if self.provider_code != 'credit_pay':
return
notification_data = {
'reference': self.reference,
'simulated_state': 'done',
'manual_capture': True,
# Distinguish manual captures from regular one-step captures.
}
self._handle_notification_data('credit_pay', notification_data)
def _send_void_request(self):
""" Override of payment to simulate a void request.
Note: self.ensure_one()
:return: None
"""
super()._send_void_request()
if self.provider_code != 'credit_pay':
return
notification_data = {'reference': self.reference,
'simulated_state': 'cancel'}
self._handle_notification_data('credit_pay', notification_data)
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 != 'credit_pay' or len(tx) == 1:
return tx
reference = notification_data.get('reference')
tx = self.search([('reference', '=', reference),
('provider_code', '=', 'credit_pay')])
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 != 'credit_pay':
return
self.provider_reference = f'credit_pay-{self.reference}'
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._credit_pay_tokenize_from_notification_data(notification_data)
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 credit_pay payment status: %s",
state))
def _credit_pay_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_details': notification_data['payment_details'],
'partner_id': self.partner_id.id,
'provider_ref': 'fake provider reference',
'verified': True,
'credit_pay_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
)