import json import logging # pyrefly: ignore [missing-import] from odoo import api, fields, models, _ # pyrefly: ignore [missing-import] from odoo.exceptions import UserError, ValidationError _logger = logging.getLogger(__name__) class EducationPaymentTransaction(models.Model): _name = 'education.payment.transaction' _description = 'Education Payment Transaction' _order = 'create_date desc, id desc' _inherit = ['mail.thread', 'mail.activity.mixin'] name = fields.Char( string='Transaction Reference', required=True, copy=False, readonly=True, default=lambda self: _('New'), index=True ) student_id = fields.Many2one( 'education.student', string='Student', required=True, ondelete='cascade', index=True, tracking=True ) company_id = fields.Many2one( 'res.company', string='Company', required=True, default=lambda self: self.env.company, index=True ) currency_id = fields.Many2one( 'res.currency', string='Currency', required=True, default=lambda self: self.env.company.currency_id ) amount = fields.Monetary( string='Amount', currency_field='currency_id', required=True, tracking=True ) provider = fields.Selection([ ('razorpay', 'Razorpay'), ('stripe', 'Stripe'), ('cashfree', 'Cashfree'), ('manual', 'Manual'), ('mock', 'Sandbox Simulator'), ], string='Payment Gateway', default='razorpay', required=True, tracking=True) razorpay_order_id = fields.Char(string='Razorpay Order ID', index=True, copy=False) razorpay_payment_id = fields.Char(string='Razorpay Payment ID', index=True, copy=False) razorpay_signature = fields.Char(string='Razorpay Signature', copy=False) status = fields.Selection([ ('pending', 'Pending'), ('authorized', 'Authorized'), ('success', 'Success'), ('failed', 'Failed'), ('cancelled', 'Cancelled'), ('refunded', 'Refunded'), ], string='Status', default='pending', required=True, index=True, tracking=True) payment_date = fields.Datetime(string='Payment Date', default=fields.Datetime.now, tracking=True) verified_on = fields.Datetime(string='Verified On', copy=False) receipt_number = fields.Char(string='Receipt Number', copy=False, tracking=True) fee_assignment_ids = fields.Many2many( 'education.fee.assignment', 'education_payment_tx_fee_rel', 'tx_id', 'fee_id', string='Paid Dues' ) payment_id = fields.Many2one('education.student.payment', string='Student Payment Record', copy=False) gateway_response = fields.Text(string='Gateway Response JSON', copy=False) error_message = fields.Text(string='Error / Failure Reason', copy=False, tracking=True) retry_count = fields.Integer(string='Retry Count', default=0, copy=False) audit_log = fields.Text(string='Audit Log', copy=False) @api.model_create_multi def create(self, vals_list): for vals in vals_list: if vals.get('name', _('New')) == _('New'): vals['name'] = self.env['ir.sequence'].next_by_code('education.payment.transaction') or _('New') if not vals.get('audit_log'): vals['audit_log'] = f"[{fields.Datetime.now()}] Transaction created in Pending state.\n" return super().create(vals_list) def log_audit(self, message): """Append audit entry with timestamp""" for rec in self: entry = f"[{fields.Datetime.now()}] {message}\n" rec.audit_log = (rec.audit_log or "") + entry rec.message_post(body=message) def action_mark_failed(self, error_reason="Payment verification failed or dismissed."): """Mark transaction failed""" for rec in self: if rec.status in ('success', 'refunded'): raise ValidationError(_("Cannot mark a successful or refunded transaction as failed.")) rec.write({ 'status': 'failed', 'error_message': error_reason, }) rec.log_audit(f"Transaction marked FAILED. Reason: {error_reason}") def action_process_refund(self): """Finance Office Action: Process refund and reverse ledger entry""" # pyrefly: ignore [missing-import] from odoo.addons.education_portal.services.payment_service import PaymentService for rec in self: if rec.status != 'success': raise UserError(_("Only successful transactions can be refunded.")) PaymentService.process_refund(rec.env, rec) return True