Prevent double-confirmation of same payment by different users

- Create pagos_confirmados table to track consumed email receipts
- PagoValidadorService now validates: valor + fecha + hora (±10min) + llave Bancolombia
- Each matched email is hashed (sha1 body+date) and marked as used; a second
  match returns estado=ya_usado instead of confirmado
- Add partial remitente name matching against the registered user's name
- GeminiVisionService prompt now extracts llave (@xxx) from the receipt image
- TelegramBotService shows distinct message for ya_usado state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro
2026-07-18 03:30:20 +00:00
co-authored by Claude Sonnet 4.6
parent 157691e166
commit ba4ec8b6cf
5 changed files with 207 additions and 26 deletions
+16 -4
View File
@@ -197,21 +197,33 @@ class TelegramBotService
return;
}
$resultado = app(PagoValidadorService::class)->validar($datosPago);
$monto = number_format($datosPago['valor'] ?? 0);
$ref = $datosPago['referencia'] ?? 'N/A';
$usuarioId = $state['user_id'] ?? null;
$nombreUsuario = $usuarioId ? (User::find($usuarioId)?->name ?? '') : '';
$resultado = app(PagoValidadorService::class)->validar(
$datosPago, $usuarioId, $nombreUsuario, 'telegram'
);
$monto = number_format($datosPago['valor'] ?? 0);
$ref = $datosPago['referencia'] ?? 'N/A';
$texto = "📊 *Resultado del análisis:*\n\n";
$texto .= "Monto: *\$$monto*\n";
$texto .= "Referencia: `{$ref}`\n";
$texto .= "Estado: *" . ($resultado['estado'] === 'confirmado' ? '✅ Confirmado' : '⚠️ No confirmado') . "*";
if ($resultado['estado'] === 'confirmado') {
$texto .= "Estado: *✅ Confirmado*";
$buttons = [
[['text' => "✅ Aplicar recarga de \$$monto", 'callback_data' => 'pay_apply|' . ($datosPago['valor'] ?? 0) . '|' . $ref]],
[['text' => '🔙 Menú principal', 'callback_data' => 'menu']],
];
} elseif ($resultado['estado'] === 'ya_usado') {
$texto .= "Estado: *⛔ Comprobante ya utilizado*\nEste pago ya fue registrado por otro usuario.";
$buttons = [
[['text' => '🧑 Hablar con asesor', 'callback_data' => 'agent']],
[['text' => '🔙 Menú principal', 'callback_data' => 'menu']],
];
} else {
$texto .= "Estado: *⚠️ No confirmado*";
$buttons = [
[['text' => '🧑 Hablar con asesor', 'callback_data' => 'agent']],
[['text' => '🔙 Menú principal', 'callback_data' => 'menu']],