# -*- coding: utf-8 -*- from odoo import models, fields, api, _ from odoo.exceptions import ValidationError from odoo.addons.skeen_citas.clinic_settings import get_settings as _clinic_settings from odoo.addons.skeen_citas.clinic_settings import url_publica as _clinic_url_publica import requests import json class SkeenPago(models.Model): _name = 'skeen.pago' _description = 'Pago de Servicio' _order = 'create_date desc' # Referencia name = fields.Char(string='Referencia', readonly=True, copy=False) # Paciente y Cita partner_id = fields.Many2one('res.partner', string='Paciente', required=True) cita_id = fields.Many2one('skeen.cita', string='Cita') # Monto amount = fields.Float(string='Monto', required=True) currency_id = fields.Many2one('res.currency', string='Moneda', default=lambda self: self.env.ref('base.MXN')) # Método de pago payment_method = fields.Selection([ ('stripe', 'Stripe'), ('mercadopago', 'MercadoPago'), ('cash', 'Efectivo'), ('card', 'Tarjeta'), ('transfer', 'Transferencia'), ('saldo_favor', 'Saldo a favor'), ('points', 'Monedero'), ], string='Método de Pago', required=True) # Estado state = fields.Selection([ ('pending', 'Pendiente'), ('processing', 'Procesando'), ('completed', 'Completado'), ('failed', 'Fallido'), ('refunded', 'Reembolsado'), ], string='Estado', default='pending') # Datos del proveedor de pago provider_reference = fields.Char(string='Referencia Proveedor') provider_response = fields.Text(string='Respuesta Proveedor') # URLs payment_url = fields.Char(string='URL de Pago') success_url = fields.Char(string='URL de Éxito') cancel_url = fields.Char(string='URL de Cancelación') # Fechas create_date = fields.Datetime(string='Fecha Creación', readonly=True, default=fields.Datetime.now) payment_date = fields.Datetime(string='Fecha de Pago') @api.model_create_multi def create(self, vals_list): for vals in vals_list: if not vals.get('name'): vals['name'] = self.env['ir.sequence'].next_by_code('skeen.pago') or 'PAGO-001' return super(SkeenPago, self).create(vals_list) def _payment_base(self, fallback): """URL pública base de la instancia para los enlaces de pago. Sale de los ajustes de la clínica (url_publica); si la instancia no está configurada, de `web.base.url` cuando es público y, en último caso, del valor que ya usaba el código (`fallback`). """ return _clinic_url_publica(self.env, fallback=fallback) def _product_name(self): """Nombre del producto en la pasarela: ' - '.""" nombre = (_clinic_settings(self.env).get('nombre') or '').strip() or 'SKEEN Derma Experts' servicio = '' if self.cita_id and self.cita_id.servicio_id: servicio = self.cita_id.servicio_id.name or '' return '%s - %s' % (nombre, servicio) if servicio else nombre def action_generate_stripe_link(self): """Generar link de pago Stripe""" self.ensure_one() stripe_key = self.env['ir.config_parameter'].sudo().get_param('skeen.stripe_secret_key') if not stripe_key: raise ValidationError(_('Stripe no está configurado!')) # Crear sesión de checkout base = self._payment_base('https://app.skeen.mx') headers = { 'Authorization': f'Bearer {stripe_key}', 'Content-Type': 'application/x-www-form-urlencoded', } data = { 'payment_method_types[]': 'card', 'line_items[0][price_data][currency]': 'mxn', 'line_items[0][price_data][product_data][name]': self._product_name(), 'line_items[0][price_data][unit_amount]': int(self.amount * 100), 'line_items[0][quantity]': 1, 'mode': 'payment', 'success_url': f'{base}/pagos/exitoso?ref={self.name}', 'cancel_url': f'{base}/pagos/cancelado?ref={self.name}', 'metadata[payment_id]': str(self.id), 'metadata[patient_phone]': self.partner_id.phone or '', } response = requests.post('https://api.stripe.com/v1/checkout/sessions', headers=headers, data=data) if response.status_code == 200: session = response.json() self.write({ 'payment_url': session['url'], 'provider_reference': session['id'], 'provider_response': json.dumps(session), 'state': 'processing', }) else: self.write({ 'provider_response': response.text, 'state': 'failed', }) return True def action_generate_mercadopago_link(self): """Generar link de pago MercadoPago""" self.ensure_one() mp_token = self.env['ir.config_parameter'].sudo().get_param('skeen.mercadopago_access_token') if not mp_token: raise ValidationError(_('MercadoPago no está configurado!')) # Crear preferencia de checkout base = self._payment_base('https://app.skeen.mx') headers = { 'Authorization': f'Bearer {mp_token}', 'Content-Type': 'application/json', } data = { 'items': [{ 'title': self._product_name(), 'quantity': 1, 'unit_price': int(self.amount), }], 'back_urls': { 'success': f'{base}/pagos/exitoso?ref={self.name}', 'failure': f'{base}/pagos/fallido?ref={self.name}', 'pending': f'{base}/pagos/pendiente?ref={self.name}', }, 'notification_url': self._payment_base('https://skeen.mx') + '/webhooks/mercadopago', 'metadata': { 'payment_id': str(self.id), 'patient_phone': self.partner_id.phone or '', }, } response = requests.post('https://api.mercadopago.com/checkout/preferences', headers=headers, json=data) if response.status_code == 201: preference = response.json() self.write({ 'payment_url': preference['init_point'], 'provider_reference': preference['id'], 'provider_response': json.dumps(preference), 'state': 'processing', }) else: self.write({ 'provider_response': response.text, 'state': 'failed', }) return True def action_confirm_payment(self): """Confirmar pago recibido""" self.ensure_one() self.write({ 'state': 'completed', 'payment_date': fields.Datetime.now(), }) # Actualizar estado de pago de la cita if self.cita_id: self.cita_id.write({ 'payment_state': 'paid', 'amount_paid': self.amount, }) # Acumular puntos en monedero (excepto si el pago es canje de monedero) if self.payment_method == 'points': return True monedero = self.env['skeen.monedero'].search([('partner_id', '=', self.partner_id.id)], limit=1) if not monedero: monedero = self.env['skeen.monedero'].create({ 'partner_id': self.partner_id.id, }) points = int(self.amount / 10) # 1 punto por cada $10 monedero.add_points( points=points, description=f'Pago de servicio - {self.cita_id.servicio_id.name if self.cita_id else "N/A"}', reference=self.name, ) return True def action_refund(self): """Procesar reembolso""" self.ensure_one() self.write({'state': 'refunded'}) # Restar puntos del monedero monedero = self.env['skeen.monedero'].search([('partner_id', '=', self.partner_id.id)], limit=1) if monedero: points = int(self.amount / 10) monedero.redeem_points( points=points, description=f'Reembolso - {self.name}', reference=self.name, ) return True