feat: web chat con IA, validación de pagos y OTP por correo
- Chat público en /chat con flujos de compra, recarga y credenciales - Verificación de identidad por código OTP enviado al correo registrado - Botones enriquecidos: cards, links, credenciales, validación de comprobante - Gemini IA para detección de intención en texto libre - Gemini Vision para extraer datos de foto de comprobante - Validador de pagos cruzando IA con correos IMAP (Bancolombia) - MercadoPago como pasarela de pago (reemplaza Wompi en el chat) - Migraciones: payload en chat_messages, user_id en chat_contacts - Config admin: API key Gemini, toggles IA y validación de foto Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
a56a69dccb
commit
536377363c
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\ChatConfig;
|
||||
use App\Models\WhatsappSystemConfig;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PagoValidadorService
|
||||
{
|
||||
/**
|
||||
* Cruza los datos extraídos por IA con los correos IMAP recientes.
|
||||
*
|
||||
* @param array $datosPago ['banco', 'valor', 'referencia', 'fecha', 'hora', 'remitente']
|
||||
* @return array ['estado' => confirmado|no_encontrado|monto_incorrecto, 'correo' => array|null]
|
||||
*/
|
||||
public function validar(array $datosPago): array
|
||||
{
|
||||
if (WhatsappSystemConfig::get('correo_imap_enabled', '0') !== '1') {
|
||||
return ['estado' => 'no_encontrado', 'correo' => null, 'motivo' => 'correo_deshabilitado'];
|
||||
}
|
||||
|
||||
try {
|
||||
$correos = $this->fetchCorreos();
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('[PagoValidador] Error leyendo correos: ' . $e->getMessage());
|
||||
return ['estado' => 'no_encontrado', 'correo' => null, 'motivo' => 'error_imap'];
|
||||
}
|
||||
|
||||
if (empty($correos)) {
|
||||
return ['estado' => 'no_encontrado', 'correo' => null, 'motivo' => 'sin_correos'];
|
||||
}
|
||||
|
||||
$valorIA = (int) ($datosPago['valor'] ?? 0);
|
||||
|
||||
foreach ($correos as $correo) {
|
||||
$datosCorreo = BancolombiaParser::parse($correo['body'] ?? '');
|
||||
|
||||
if (! $datosCorreo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$valorCorreo = (int) ($datosCorreo['valor'] ?? 0);
|
||||
|
||||
// Verificar referencia si ambas están disponibles
|
||||
if (! empty($datosPago['referencia']) && ! empty($datosCorreo['referencia'])) {
|
||||
if ($datosPago['referencia'] === $datosCorreo['referencia']) {
|
||||
return ['estado' => 'confirmado', 'correo' => $datosCorreo];
|
||||
}
|
||||
}
|
||||
|
||||
// Verificar valor
|
||||
if ($valorIA > 0 && $valorCorreo > 0) {
|
||||
$tolerancia = (int) ChatConfig::get('validacion_monto_tolerancia', '0');
|
||||
|
||||
if (abs($valorIA - $valorCorreo) <= $tolerancia) {
|
||||
return ['estado' => 'confirmado', 'correo' => $datosCorreo];
|
||||
} else {
|
||||
return ['estado' => 'monto_incorrecto', 'correo' => $datosCorreo,
|
||||
'esperado' => $valorCorreo, 'recibido' => $valorIA];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ['estado' => 'no_encontrado', 'correo' => null, 'motivo' => 'sin_coincidencia'];
|
||||
}
|
||||
|
||||
private function fetchCorreos(): array
|
||||
{
|
||||
$minutos = (int) WhatsappSystemConfig::get('correo_imap_minutos', '5');
|
||||
|
||||
$service = new CorreoImapService(
|
||||
host: WhatsappSystemConfig::get('correo_imap_host', ''),
|
||||
port: (int) WhatsappSystemConfig::get('correo_imap_port', '993'),
|
||||
useSsl: WhatsappSystemConfig::get('correo_imap_ssl', '1') === '1',
|
||||
user: WhatsappSystemConfig::get('correo_imap_user', ''),
|
||||
password: WhatsappSystemConfig::get('correo_imap_password', ''),
|
||||
folder: WhatsappSystemConfig::get('correo_imap_folder', 'INBOX'),
|
||||
);
|
||||
|
||||
return $service->fetchLatest(limit: 10, minutesBack: $minutos);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user