feat: sistema de solicitud de recarga con auto-aplicación y botón Reintentar
- SolicitudRecarga: modelo + migración para atar cada intento de recarga al usuario (previene cruce de pagos entre usuarios) - PublicChat: mostrarTransferencia crea solicitud, updatedFotoComprobante valida contra ella, auto-aplica sin botón extra - Botón Reintentar en web chat cuando el correo aún no llega (pago_pendiente en flujoData) - aplicarRecargaValidada corregido: error de sintaxis por fragmento duplicado eliminado Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
d5e497bfc5
commit
a7d7c32350
@@ -507,48 +507,67 @@ class PublicChat extends Component
|
||||
$monto = (int) ($datosPago['valor'] ?? 0);
|
||||
$botones = [];
|
||||
|
||||
// Verificar solicitud activa
|
||||
$solicitud = $this->userId ? \App\Models\SolicitudRecarga::pendiente($this->userId) : null;
|
||||
if (! $solicitud) {
|
||||
$this->guardarMensajeBot($this->convId, "No tienes ninguna solicitud de recarga activa. Inicia una recarga desde el menu para poder enviar el comprobante.");
|
||||
$this->cargarMensajes();
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificar que el monto coincida con la solicitud
|
||||
$valorIA = (int) ($datosPago['valor'] ?? 0);
|
||||
if ($valorIA !== $solicitud->monto) {
|
||||
$this->guardarMensajeBot($this->convId, "El monto del comprobante ($" . number_format($valorIA) . ") no coincide con tu solicitud de recarga ($" . number_format($solicitud->monto) . ").");
|
||||
$this->cargarMensajes();
|
||||
return;
|
||||
}
|
||||
|
||||
$motivo = $resultado['motivo'] ?? '';
|
||||
|
||||
if ($resultado['estado'] === 'confirmado') {
|
||||
$botones[] = [
|
||||
'label' => 'Aplicar recarga de $' . number_format($monto),
|
||||
'action' => 'pago.aplicar',
|
||||
'data' => ['monto' => $monto, 'referencia' => $datosPago['referencia'] ?? null],
|
||||
];
|
||||
$botones[] = ['label' => '<- Menu', 'action' => 'menu.principal', 'data' => []];
|
||||
$motivoTexto = null;
|
||||
$this->autoAplicarRecargaWeb($solicitud, $solicitud->monto);
|
||||
return;
|
||||
|
||||
} elseif ($resultado['estado'] === 'ya_usado') {
|
||||
$this->flujo = '';
|
||||
$this->flujoData = [];
|
||||
$this->guardarMensajeBot($this->convId, "⛔ Comprobante ya utilizado. Este pago ya fue registrado anteriormente.");
|
||||
|
||||
} elseif ($motivo === 'sin_coincidencia' || $motivo === 'sin_correos') {
|
||||
// Guardar datosPago para reintentar sin reenviar foto
|
||||
$this->flujo = 'pago.pendiente';
|
||||
$this->flujoData = ['datosPago' => $datosPago];
|
||||
$this->flujoData = array_merge($this->flujoData, ['datosPago' => $datosPago]);
|
||||
|
||||
ChatMessage::create([
|
||||
'conversation_id' => $this->convId,
|
||||
'tipo' => 'bot',
|
||||
'tipo_ui' => 'validacion',
|
||||
'contenido' => 'Por ahora no encontramos tu pago de confirmacion. Puede demorar entre 1 y 2 minutos en llegar. Espera un momento y presiona Reintentar.',
|
||||
'payload' => [
|
||||
'estado' => 'pendiente',
|
||||
'motivo' => 'Por ahora no encontramos tu pago de confirmacion. Puede demorar entre 1 y 2 minutos.',
|
||||
'ia_datos' => $datosPago,
|
||||
'botones' => [
|
||||
['label' => '🔄 Reintentar', 'action' => 'pago.reintentar', 'data' => []],
|
||||
['label' => '<- Menu', 'action' => 'menu.principal', 'data' => []],
|
||||
],
|
||||
],
|
||||
'leido' => true,
|
||||
]);
|
||||
$this->cargarMensajes();
|
||||
$this->dispatchBrowserEvent('scroll-chat');
|
||||
return;
|
||||
|
||||
$botones[] = ['label' => '🔄 Reintentar', 'action' => 'pago.reintentar', 'data' => []];
|
||||
$botones[] = ['label' => '<- Menu', 'action' => 'menu.principal', 'data' => []];
|
||||
$motivoTexto = 'Por ahora no encontramos tu pago de confirmación. Puede demorar entre 1 y 2 minutos en llegar. Espera un momento y presiona Reintentar.';
|
||||
} else {
|
||||
$botones[] = ['label' => '<- Menu', 'action' => 'menu.principal', 'data' => []];
|
||||
$motivoTexto = match ($motivo) {
|
||||
'correo_deshabilitado' => 'Verificación por correo no configurada',
|
||||
'correo_deshabilitado' => 'Verificacion por correo no configurada',
|
||||
'error_imap' => 'Error al leer correos IMAP',
|
||||
'error_sistema' => 'Error interno, intenta de nuevo en unos minutos',
|
||||
default => 'No se pudo verificar automáticamente',
|
||||
default => 'No se pudo verificar automaticamente',
|
||||
};
|
||||
$this->guardarMensajeBot($this->convId, "⚠️ No confirmado: {$motivoTexto}");
|
||||
}
|
||||
|
||||
ChatMessage::create([
|
||||
'conversation_id' => $this->convId,
|
||||
'tipo' => 'bot',
|
||||
'tipo_ui' => 'validacion',
|
||||
'contenido' => 'Resultado del analisis:',
|
||||
'payload' => [
|
||||
'estado' => $resultado['estado'],
|
||||
'motivo' => $motivoTexto,
|
||||
'ia_datos' => $datosPago,
|
||||
'botones' => $botones,
|
||||
],
|
||||
'leido' => true,
|
||||
]);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$this->guardarMensajeBot($this->convId, "Error analizando el comprobante. Por favor intenta de nuevo.");
|
||||
} finally {
|
||||
@@ -1115,6 +1134,14 @@ class PublicChat extends Component
|
||||
]);
|
||||
|
||||
$this->guardarMensajeBot($this->convId, "Cuando hayas transferido, envia el comprobante usando el icono de camara (📷) y el sistema lo validara automaticamente.\n\nTienes hasta 15 minutos despues de la transferencia para enviar el soporte.");
|
||||
|
||||
// Registrar solicitud pendiente
|
||||
if ($this->userId) {
|
||||
$solicitud = \App\Models\SolicitudRecarga::crear($this->userId, $monto, 'web');
|
||||
$this->flujo = 'pago.solicitud';
|
||||
$this->flujoData = ['solicitud_id' => $solicitud->id, 'monto' => $monto];
|
||||
}
|
||||
|
||||
$this->volver('menu.principal');
|
||||
}
|
||||
|
||||
@@ -1329,63 +1356,86 @@ class PublicChat extends Component
|
||||
private function reintentarValidacionPago(): void
|
||||
{
|
||||
if ($this->flujo !== 'pago.pendiente' || empty($this->flujoData['datosPago'])) {
|
||||
$this->guardarMensajeBot($this->convId, 'No hay ningún comprobante pendiente. Envía la foto de tu comprobante.');
|
||||
$this->guardarMensajeBot($this->convId, 'No hay ningun comprobante pendiente. Envia la foto de tu comprobante.');
|
||||
return;
|
||||
}
|
||||
|
||||
$solicitud = $this->userId ? \App\Models\SolicitudRecarga::pendiente($this->userId) : null;
|
||||
if (! $solicitud) {
|
||||
$this->flujo = '';
|
||||
$this->flujoData = [];
|
||||
$this->guardarMensajeBot($this->convId, '⚠️ Tu solicitud de recarga expiro. Inicia una nueva recarga desde el menu.');
|
||||
$this->cargarMensajes();
|
||||
return;
|
||||
}
|
||||
|
||||
$datosPago = $this->flujoData['datosPago'];
|
||||
$monto = (int) ($datosPago['valor'] ?? 0);
|
||||
$resultado = app(\App\Services\PagoValidadorService::class)->validar($datosPago, $this->userId, $this->nombreUsuario, 'web');
|
||||
$motivo = $resultado['motivo'] ?? '';
|
||||
$botones = [];
|
||||
|
||||
if ($resultado['estado'] === 'confirmado') {
|
||||
$this->flujo = '';
|
||||
$this->flujoData = [];
|
||||
$botones[] = [
|
||||
'label' => 'Aplicar recarga de $' . number_format($monto),
|
||||
'action' => 'pago.aplicar',
|
||||
'data' => ['monto' => $monto, 'referencia' => $datosPago['referencia'] ?? null],
|
||||
];
|
||||
$botones[] = ['label' => '<- Menu', 'action' => 'menu.principal', 'data' => []];
|
||||
$motivoTexto = null;
|
||||
$this->autoAplicarRecargaWeb($solicitud, $solicitud->monto);
|
||||
return;
|
||||
|
||||
} elseif ($resultado['estado'] === 'ya_usado') {
|
||||
$this->flujo = '';
|
||||
$this->flujoData = [];
|
||||
$botones[] = ['label' => '<- Menu', 'action' => 'menu.principal', 'data' => []];
|
||||
$motivoTexto = 'Este comprobante ya fue registrado anteriormente.';
|
||||
$this->guardarMensajeBot($this->convId, '⛔ Comprobante ya utilizado. Este pago ya fue registrado anteriormente.');
|
||||
|
||||
} elseif ($motivo === 'sin_coincidencia' || $motivo === 'sin_correos') {
|
||||
$botones[] = ['label' => '🔄 Reintentar', 'action' => 'pago.reintentar', 'data' => []];
|
||||
$botones[] = ['label' => '<- Menu', 'action' => 'menu.principal', 'data' => []];
|
||||
$motivoTexto = 'Aún no encontramos tu pago. Puede demorar entre 1 y 2 minutos en llegar. Espera un momento y presiona Reintentar.';
|
||||
ChatMessage::create([
|
||||
'conversation_id' => $this->convId,
|
||||
'tipo' => 'bot',
|
||||
'tipo_ui' => 'validacion',
|
||||
'contenido' => 'Aun no encontramos tu pago.',
|
||||
'payload' => [
|
||||
'estado' => 'pendiente',
|
||||
'motivo' => 'Aun no encontramos tu pago. Puede demorar entre 1 y 2 minutos en llegar.',
|
||||
'ia_datos' => $datosPago,
|
||||
'botones' => [
|
||||
['label' => '🔄 Reintentar', 'action' => 'pago.reintentar', 'data' => []],
|
||||
['label' => '<- Menu', 'action' => 'menu.principal', 'data' => []],
|
||||
],
|
||||
],
|
||||
'leido' => true,
|
||||
]);
|
||||
$this->cargarMensajes();
|
||||
$this->dispatchBrowserEvent('scroll-chat');
|
||||
return;
|
||||
|
||||
} else {
|
||||
$this->flujo = '';
|
||||
$this->flujoData = [];
|
||||
$botones[] = ['label' => '<- Menu', 'action' => 'menu.principal', 'data' => []];
|
||||
$motivoTexto = 'No se pudo verificar el pago. Intenta de nuevo.';
|
||||
$this->guardarMensajeBot($this->convId, '⚠️ No se pudo verificar el pago. Intenta de nuevo.');
|
||||
}
|
||||
|
||||
ChatMessage::create([
|
||||
'conversation_id' => $this->convId,
|
||||
'tipo' => 'bot',
|
||||
'tipo_ui' => 'validacion',
|
||||
'contenido' => 'Verificando de nuevo...',
|
||||
'payload' => [
|
||||
'estado' => $resultado['estado'],
|
||||
'motivo' => $motivoTexto,
|
||||
'ia_datos' => $datosPago,
|
||||
'botones' => $botones,
|
||||
],
|
||||
'leido' => true,
|
||||
]);
|
||||
|
||||
$this->cargarMensajes();
|
||||
$this->dispatchBrowserEvent('scroll-chat');
|
||||
}
|
||||
|
||||
private function autoAplicarRecargaWeb(\App\Models\SolicitudRecarga $solicitud, int $monto): void
|
||||
{
|
||||
$user = User::with('saldo')->find($this->userId);
|
||||
$saldo = $user->saldo ?? Saldo::create(['usuario_id' => $this->userId, 'valor' => 0]);
|
||||
$nuevoSaldo = $saldo->valor + $monto;
|
||||
$saldo->update(['valor' => $nuevoSaldo]);
|
||||
$this->saldoUsuario = $nuevoSaldo;
|
||||
|
||||
$solicitud->confirmar();
|
||||
|
||||
$this->flujo = '';
|
||||
$this->flujoData = [];
|
||||
|
||||
$this->guardarMensajeBot(
|
||||
$this->convId,
|
||||
"✅ ¡Tu recarga de $" . number_format($monto) . " fue aplicada exitosamente!\nTu nuevo saldo es $" . number_format($nuevoSaldo) . "."
|
||||
);
|
||||
$this->cargarMensajes();
|
||||
$this->dispatchBrowserEvent('scroll-chat');
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
|
||||
private function aplicarRecargaValidada(array $data): void
|
||||
{
|
||||
if (! $this->userId) {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SolicitudRecarga extends Model
|
||||
{
|
||||
protected $table = 'solicitudes_recarga';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = ['user_id', 'monto', 'canal', 'estado'];
|
||||
|
||||
protected $casts = ['created_at' => 'datetime'];
|
||||
|
||||
public static function crear(int $userId, int $monto, string $canal): static
|
||||
{
|
||||
// Expirar cualquier solicitud pendiente anterior del mismo usuario
|
||||
static::where('user_id', $userId)
|
||||
->where('estado', 'pendiente')
|
||||
->update(['estado' => 'expirada']);
|
||||
|
||||
return static::create([
|
||||
'user_id' => $userId,
|
||||
'monto' => $monto,
|
||||
'canal' => $canal,
|
||||
'estado' => 'pendiente',
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pendiente(int $userId): ?static
|
||||
{
|
||||
return static::where('user_id', $userId)
|
||||
->where('estado', 'pendiente')
|
||||
->where('created_at', '>=', now()->subMinutes(30))
|
||||
->latest('id')
|
||||
->first();
|
||||
}
|
||||
|
||||
public function confirmar(): void
|
||||
{
|
||||
$this->update(['estado' => 'confirmada']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('solicitudes_recarga', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('user_id');
|
||||
$table->integer('monto');
|
||||
$table->string('canal', 20)->default('telegram');
|
||||
$table->string('estado', 20)->default('pendiente'); // pendiente | confirmada | expirada
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
|
||||
$table->index(['user_id', 'estado']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('solicitudes_recarga');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user