diff --git a/app/Http/Livewire/Chat/PublicChat.php b/app/Http/Livewire/Chat/PublicChat.php index 21e6f90..0014f6b 100755 --- a/app/Http/Livewire/Chat/PublicChat.php +++ b/app/Http/Livewire/Chat/PublicChat.php @@ -487,15 +487,12 @@ class PublicChat extends Component $action === 'servicios.ver' => $this->verServicio((int) ($data['id'] ?? 0)), $action === 'compra.iniciar' => $this->iniciarCompra((int) ($data['tarifa_id'] ?? 0)), $action === 'compra.pagar.saldo' => $this->pagarConSaldo(), - $action === 'compra.pagar.mp' => $this->pagarConMP(), $action === 'promo.listar' => $this->listarPromociones(), $action === 'promo.ver' => $this->verPromocion((int) ($data['id'] ?? 0)), $action === 'promo.comprar.saldo' => $this->comprarPromoConSaldo((int) ($data['id'] ?? 0)), - $action === 'promo.comprar.mp' => $this->comprarPromoConMP((int) ($data['id'] ?? 0)), $action === 'recarga.iniciar' => $this->mostrarFormRecarga(), $action === 'recarga.otro_valor' => $this->solicitarMontoPersonalizado(), $action === 'recarga.elegir_metodo' => $this->elegirMetodoRecarga((int) ($data['monto'] ?? 0)), - $action === 'recarga.mp' => $this->generarLinkRecargaMP((int) ($data['monto'] ?? 0)), $action === 'recarga.transferencia' => $this->mostrarTransferencia((int) ($data['monto'] ?? 0)), $action === 'credenciales.listar' => $this->listarCredenciales(), $action === 'historial.ver' => $this->verHistorial(), @@ -596,24 +593,12 @@ class PublicChat extends Component } elseif ($motivo === 'sin_coincidencia' || $motivo === 'sin_correos') { $this->flujo = 'pago.pendiente'; - $this->flujoData = array_merge($this->flujoData, ['datosPago' => $datosPago]); + $this->flujoData = array_merge($this->flujoData, ['datosPago' => $datosPago, 'intentos' => 0]); - 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->guardarMensajeBot($this->convId, + "β³ *Estamos validando tu pago*, esto puede demorar unos minutos.\n" + . "Cuando terminemos te confirmaremos por este medio. Espera un momento." + ); $this->cargarMensajes(); $this->dispatchBrowserEvent('scroll-chat'); return; @@ -788,14 +773,27 @@ class PublicChat extends Component ]); } else { $falta = $precio - (float) $this->saldoUsuario; + + // Guardar compra pendiente para ejecutarla automΓ‘ticamente tras recargar + $this->flujoData = array_merge($this->flujoData, [ + 'pending_purchase' => [ + 'type' => 'tarifa', + 'tarifa_id' => $tarifaId, + 'valor' => $precio, + 'servicio' => $tarifa->servicio->nombre, + 'dias' => $tarifa->dias, + 'pantallas' => $tarifa->pantallas, + ], + ]); + ChatMessage::create([ 'conversation_id' => $this->convId, 'tipo' => 'bot', 'tipo_ui' => 'buttons', - 'contenido' => "{$tarifa->servicio->nombre} - {$detalle}\nPrecio: $" . number_format($precio) . "\n\nSaldo actual: $" . number_format($this->saldoUsuario) . " β te faltan $" . number_format($falta) . "\n\nRecarga tu saldo para continuar:", + 'contenido' => "{$tarifa->servicio->nombre} - {$detalle}\nPrecio: $" . number_format($precio) . "\n\nSaldo actual: $" . number_format($this->saldoUsuario) . " β te faltan $" . number_format($falta) . "\n\nRecarga para comprar:", 'payload' => ['botones' => [ - ['label' => 'Recargar saldo', 'action' => 'recarga.iniciar', 'data' => []], - ['label' => 'Volver', 'action' => 'servicios.listar', 'data' => []], + ['label' => "π° Recargar $" . number_format($falta) . " para comprar {$tarifa->servicio->nombre}", 'action' => 'recarga.iniciar', 'data' => []], + ['label' => 'Volver', 'action' => 'servicios.listar', 'data' => []], ]], 'leido' => true, ]); @@ -876,35 +874,6 @@ class PublicChat extends Component $this->volver('menu.principal'); } - private function pagarConMP(): void - { - if ($this->flujo !== 'compra') { - return; - } - - $valor = (int) ($this->flujoData['valor'] ?? 0); - $servicio = $this->flujoData['servicio'] ?? 'Plan streaming'; - $ref = 'CHAT-' . strtoupper(Str::random(12)); - $url = $this->crearPreferenciaMP($servicio, $valor, $ref); - - if (! $url) { - $this->guardarMensajeBot($this->convId, "No se pudo generar el link de pago. Intenta de nuevo o contacta a un asesor."); - return; - } - - ChatMessage::create([ - 'conversation_id' => $this->convId, - 'tipo' => 'bot', - 'tipo_ui' => 'link', - 'contenido' => "Link de pago por $" . number_format($valor) . " listo. Despues de pagar, envia el comprobante aqui.", - 'payload' => ['url' => $url, 'label' => 'Pagar $' . number_format($valor) . ' con MercadoPago', 'nota' => 'Ref: ' . $ref], - 'leido' => true, - ]); - - $this->flujo = ''; - $this->flujoData = []; - $this->volver('menu.principal'); - } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Promociones @@ -988,14 +957,25 @@ class PublicChat extends Component ]); } else { $falta = $precio - (float) $this->saldoUsuario; + + // Guardar compra pendiente para ejecutarla automΓ‘ticamente tras recargar + $this->flujoData = array_merge($this->flujoData, [ + 'pending_purchase' => [ + 'type' => 'promo', + 'promo_id' => $id, + 'valor' => $precio, + 'nombre' => $promo->nombre ?? 'PromociΓ³n', + ], + ]); + ChatMessage::create([ 'conversation_id' => $this->convId, 'tipo' => 'bot', 'tipo_ui' => 'buttons', - 'contenido' => ($promo->nombre ?? 'Promocion') . "\n$" . number_format($precio) . "\n\nSaldo actual: $" . number_format($this->saldoUsuario) . " β te faltan $" . number_format($falta) . "\n\nRecarga tu saldo para continuar:", + 'contenido' => ($promo->nombre ?? 'Promocion') . "\n$" . number_format($precio) . "\n\nSaldo actual: $" . number_format($this->saldoUsuario) . " β te faltan $" . number_format($falta) . "\n\nRecarga para comprar:", 'payload' => ['botones' => [ - ['label' => 'Recargar saldo', 'action' => 'recarga.iniciar', 'data' => []], - ['label' => 'Volver', 'action' => 'promo.listar', 'data' => []], + ['label' => "π° Recargar $" . number_format($falta) . " para comprar " . ($promo->nombre ?? 'Promo'), 'action' => 'recarga.iniciar', 'data' => []], + ['label' => 'Volver', 'action' => 'promo.listar', 'data' => []], ]], 'leido' => true, ]); @@ -1076,42 +1056,6 @@ class PublicChat extends Component $this->volver('menu.principal'); } - private function comprarPromoConMP(int $promoId): void - { - $rolId = $this->userId - ? User::find($this->userId)?->rol_id - : Role::where('nombre', 'cliente')->value('id'); - - $promo = Promociones::with(['tarifaPromo' => fn ($q) => $q->where('rol_id', $rolId)])->find($promoId); - if (! $promo) { - return; - } - - $upric = $this->userId - ? Usuario_promo::where('usuario_id', $this->userId)->where('promocion_id', $promoId)->first() - : null; - $tp = $promo->tarifaPromo->first(); - $precio = $upric ? $upric->precio : ($tp ? $tp->precio : $promo->precio); - - $ref = 'PROMO-' . strtoupper(Str::random(10)); - $url = $this->crearPreferenciaMP($promo->nombre ?? 'Promocion streaming', (int) $precio, $ref); - - if (! $url) { - $this->guardarMensajeBot($this->convId, "No se pudo generar el link de pago. Intenta de nuevo o contacta a un asesor."); - return; - } - - ChatMessage::create([ - 'conversation_id' => $this->convId, - 'tipo' => 'bot', - 'tipo_ui' => 'link', - 'contenido' => "Link de pago por $" . number_format($precio) . ". Envia el comprobante aqui al pagar.", - 'payload' => ['url' => $url, 'label' => 'Pagar con MercadoPago', 'nota' => 'Ref: ' . $ref], - 'leido' => true, - ]); - - $this->volver('menu.principal'); - } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Recarga de saldo @@ -1149,15 +1093,21 @@ class PublicChat extends Component return; } + $llave = \App\Models\ChatConfig::get('recarga_banco_llave', ''); + if (! $llave) { + $this->guardarMensajeBot($this->convId, "β οΈ La recarga no estΓ‘ disponible en este momento. Contacta a un asesor."); + $this->volver('menu.principal'); + return; + } + ChatMessage::create([ 'conversation_id' => $this->convId, 'tipo' => 'bot', 'tipo_ui' => 'buttons', 'contenido' => "Recarga de $" . number_format($monto) . "\nΒΏComo quieres pagar?", 'payload' => ['botones' => [ - ['label' => 'MercadoPago', 'action' => 'recarga.mp', 'data' => ['monto' => $monto]], - ['label' => 'Bre-B', 'action' => 'recarga.transferencia', 'data' => ['monto' => $monto]], - ['label' => 'Volver', 'action' => 'recarga.iniciar', 'data' => []], + ['label' => 'π Bre-B', 'action' => 'recarga.transferencia', 'data' => ['monto' => $monto]], + ['label' => 'Volver', 'action' => 'recarga.iniciar', 'data' => []], ]], 'leido' => true, ]); @@ -1219,7 +1169,7 @@ class PublicChat extends Component $titular = \App\Models\ChatConfig::get('recarga_banco_titular', ''); if (! $clave) { - $this->guardarMensajeBot($this->convId, "El pago por Bre-B no esta disponible en este momento. Usa MercadoPago o contacta a un asesor."); + $this->guardarMensajeBot($this->convId, "El pago por Bre-B no esta disponible en este momento. Contacta a un asesor."); return; } @@ -1237,7 +1187,14 @@ class PublicChat extends Component 'leido' => true, ]); - $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."); + $this->guardarMensajeBot($this->convId, + "π *Al enviar el comprobante, asegΓΊrate de que se vea claramente:*\n" + . "β Tu nombre completo\n" + . "β El valor transferido (\$" . number_format($monto) . ")\n" + . "β La fecha y hora de la transacciΓ³n\n\n" + . "Cuando hayas transferido, envia el comprobante usando el icono de camara (π·) y el sistema lo validara automaticamente.\n\n" + . "Tienes hasta el final del dia para enviar el soporte." + ); if ($this->userId) { $nombre = $this->flujoData['nombre_remitente'] ?? null; @@ -1249,87 +1206,6 @@ class PublicChat extends Component $this->volver('menu.principal'); } - private function generarLinkRecargaMP(int $monto): void - { - if ($monto <= 0) { - $this->guardarMensajeBot($this->convId, "Monto invalido."); - return; - } - - $ref = 'REC-' . strtoupper(Str::random(12)); - $url = $this->crearPreferenciaMP('Recarga de saldo', $monto, $ref); - - if (! $url) { - $this->guardarMensajeBot($this->convId, "No se pudo generar el link de recarga. Intenta de nuevo o contacta a un asesor."); - return; - } - - if ($this->userId) { - $user = User::with('saldo')->find($this->userId); - $saldo = $user->saldo ?? Saldo::create(['usuario_id' => $this->userId, 'valor' => 0]); - recarga::create([ - 'monto' => $monto, - 'reference' => $ref, - 'status' => 'pendiente_mp', - 'usuario_id' => $this->userId, - 'saldo_id' => $saldo->id, - ]); - } - - ChatMessage::create([ - 'conversation_id' => $this->convId, - 'tipo' => 'bot', - 'tipo_ui' => 'link', - 'contenido' => "Tu link de recarga por $" . number_format($monto) . " esta listo. Despues de pagar, envia el comprobante aqui.", - 'payload' => ['url' => $url, 'label' => 'Recargar $' . number_format($monto) . ' con MercadoPago', 'nota' => 'Ref: ' . $ref], - 'leido' => true, - ]); - - $this->volver('menu.principal'); - } - - private function crearPreferenciaMP(string $titulo, int $valor, string $ref): ?string - { - $accessToken = config('services.mercadopago.token'); - - if (! $accessToken) { - return null; - } - - try { - $response = \Illuminate\Support\Facades\Http::withToken($accessToken) - ->timeout(10) - ->post('https://api.mercadopago.com/checkout/preferences', [ - 'items' => [[ - 'title' => $titulo, - 'quantity' => 1, - 'unit_price' => $valor, - 'currency_id' => 'COP', - ]], - 'external_reference' => $ref, - 'back_urls' => [ - 'success' => url('/chat'), - 'failure' => url('/chat'), - 'pending' => url('/chat'), - ], - 'auto_return' => 'approved', - 'payment_methods' => [ - 'excluded_payment_types' => [['id' => 'ticket']], - ], - ]); - - if ($response->successful()) { - return $response->json('init_point'); - } - - \Illuminate\Support\Facades\Log::warning('[ChatMP] Error creando preferencia: ' . $response->body()); - return null; - - } catch (\Throwable $e) { - \Illuminate\Support\Facades\Log::warning('[ChatMP] Excepcion: ' . $e->getMessage()); - return null; - } - } // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Credenciales @@ -1457,6 +1333,73 @@ class PublicChat extends Component // Aplicar recarga validada por IA // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + // Llamado por wire:poll cada 20s β solo actΓΊa cuando hay un pago pendiente de validar + public function autoReintentarPago(): void + { + if ($this->flujo !== 'pago.pendiente' || empty($this->flujoData['datosPago'])) { + return; + } + + $maxIntentos = 9; + $intentos = (int) ($this->flujoData['intentos'] ?? 0); + + // Agotados β mostrar botΓ³n manual una sola vez + if ($intentos >= $maxIntentos) { + if (! ($this->flujoData['boton_manual_mostrado'] ?? false)) { + $this->flujoData['boton_manual_mostrado'] = true; + ChatMessage::create([ + 'conversation_id' => $this->convId, + 'tipo' => 'bot', + 'tipo_ui' => 'validacion', + 'contenido' => 'β οΈ No pudimos confirmar tu pago automΓ‘ticamente. Si ya realizaste la transferencia, presiona Reintentar.', + 'payload' => [ + 'estado' => 'pendiente', + 'motivo' => 'No pudimos confirmar automΓ‘ticamente.', + 'botones' => [ + ['label' => 'π Reintentar', 'action' => 'pago.reintentar', 'data' => []], + ['label' => '<- MenΓΊ', 'action' => 'menu.principal', 'data' => []], + ], + ], + 'leido' => true, + ]); + $this->cargarMensajes(); + $this->dispatchBrowserEvent('scroll-chat'); + } + return; + } + + $this->flujoData['intentos'] = $intentos + 1; + + $solicitud = $this->userId ? \App\Models\SolicitudRecarga::pendiente($this->userId) : null; + if (! $solicitud) { + $this->flujo = ''; + $this->flujoData = []; + $this->guardarMensajeBot($this->convId, 'β οΈ Tu solicitud de recarga expirΓ³. Inicia una nueva desde el menΓΊ.'); + $this->cargarMensajes(); + return; + } + + $datosPago = $this->flujoData['datosPago']; + $nombreParaValidar = $this->flujoData['nombre_remitente'] ?? $this->nombreUsuario; + $resultado = app(\App\Services\PagoValidadorService::class)->validar($datosPago, $this->userId, $nombreParaValidar, 'web'); + $motivo = $resultado['motivo'] ?? ''; + + if ($resultado['estado'] === 'confirmado') { + $this->autoAplicarRecargaWeb($solicitud, $solicitud->monto); + return; + } + + if ($resultado['estado'] === 'ya_usado') { + $this->flujo = ''; + $this->flujoData = []; + $this->guardarMensajeBot($this->convId, 'β Comprobante ya utilizado. Este pago ya fue registrado anteriormente.'); + $this->cargarMensajes(); + return; + } + + // Sigue sin encontrarse β el poll volverΓ‘ a llamar este mΓ©todo en 20s + } + private function reintentarValidacionPago(): void { if ($this->flujo !== 'pago.pendiente' || empty($this->flujoData['datosPago'])) { @@ -1537,6 +1480,7 @@ class PublicChat extends Component $solicitud->confirmar(); + $pp = $this->flujoData['pending_purchase'] ?? null; $this->flujo = ''; $this->flujoData = []; @@ -1544,6 +1488,21 @@ class PublicChat extends Component $this->convId, "β Β‘Tu recarga de $" . number_format($monto) . " fue aplicada exitosamente!\nTu nuevo saldo es $" . number_format($nuevoSaldo) . "." ); + + if ($pp && ($pp['type'] ?? '') === 'tarifa') { + $this->flujo = 'compra'; + $this->flujoData = [ + 'tarifa_id' => $pp['tarifa_id'], + 'valor' => $pp['valor'], + 'servicio' => $pp['servicio'], + 'dias' => $pp['dias'], + 'pantallas' => $pp['pantallas'], + ]; + $this->pagarConSaldo(); + } elseif ($pp && ($pp['type'] ?? '') === 'promo') { + $this->comprarPromoConSaldo($pp['promo_id']); + } + $this->cargarMensajes(); $this->dispatchBrowserEvent('scroll-chat'); } diff --git a/app/Jobs/ValidarPagoTelegramJob.php b/app/Jobs/ValidarPagoTelegramJob.php new file mode 100644 index 0000000..0f23c4d --- /dev/null +++ b/app/Jobs/ValidarPagoTelegramJob.php @@ -0,0 +1,148 @@ +chatId}"; + $state = Cache::get($cacheKey, []); + $usuarioId = $state['user_id'] ?? null; + $bot = new TelegramBotService(); + + if (! $usuarioId) { + return; + } + + $solicitud = SolicitudRecarga::pendiente($usuarioId); + if (! $solicitud) { + Log::info("[ValidarPagoTelegramJob] chatId={$this->chatId}: solicitud ya no estΓ‘ pendiente, abortando."); + return; + } + + $nombreUsuario = $state['data']['nombre_remitente'] ?? null; + + $resultado = app(PagoValidadorService::class)->validar( + $this->datosPago, $usuarioId, $nombreUsuario, 'telegram' + ); + + if ($resultado['estado'] === 'confirmado') { + $this->aplicarYNotificar($bot, $state, $cacheKey, $solicitud, $usuarioId); + return; + } + + if ($resultado['estado'] === 'ya_usado') { + $bot->sendWithKeyboard($this->chatId, + "β *Comprobante ya utilizado.*\nEste pago ya fue registrado anteriormente.", + [[['text' => 'π MenΓΊ principal', 'callback_data' => 'menu']]] + ); + return; + } + + // No encontrado β reintentar o agotar + if ($this->intento < self::MAX_INTENTOS) { + Log::info("[ValidarPagoTelegramJob] chatId={$this->chatId}: intento {$this->intento}/" . self::MAX_INTENTOS . " β reintentando en 20s"); + self::dispatch($this->chatId, $this->datosPago, $this->intento + 1) + ->delay(now()->addSeconds(20)); + } else { + Log::info("[ValidarPagoTelegramJob] chatId={$this->chatId}: intentos agotados, mostrando botΓ³n manual"); + $bot->sendWithKeyboard( + $this->chatId, + "β οΈ No pudimos confirmar tu pago automΓ‘ticamente.\nSi ya realizaste la transferencia, presiona Reintentar.", + [ + [['text' => 'π Reintentar', 'callback_data' => 'pay_retry']], + [['text' => 'π MenΓΊ principal', 'callback_data' => 'menu']], + ] + ); + } + } + + private function aplicarYNotificar( + TelegramBotService $bot, + array $state, + string $cacheKey, + SolicitudRecarga $solicitud, + int $usuarioId + ): void { + $user = User::with('saldo')->find($usuarioId); + $saldo = $user->saldo ?? Saldo::create(['usuario_id' => $usuarioId, 'valor' => 0]); + $monto = $solicitud->monto; + $nuevoSaldo = $saldo->valor + $monto; + + $saldo->update(['valor' => $nuevoSaldo]); + + recarga::create([ + 'usuario_id' => $usuarioId, + 'saldo_id' => $saldo->id, + 'monto' => $monto, + 'valor_recarga' => $monto, + 'status' => 'Confirmado', + 'reference' => 'breb-bot-' . $solicitud->id, + ]); + + $solicitud->confirmar(); + + $pp = $state['data']['pending_purchase'] ?? null; + unset($state['data']['pago_pendiente'], $state['data']['solicitud_recarga_id'], $state['data']['pending_purchase']); + Cache::put($cacheKey, $state, now()->addSeconds(self::STATE_TTL)); + + $bot->send($this->chatId, + "β *Β‘Tu recarga de \$" . number_format($monto) . " fue aplicada exitosamente!*\n" + . "Tu nuevo saldo es *\$" . number_format($nuevoSaldo) . "*" + ); + + if ($pp && ($pp['type'] ?? '') === 'tarifa') { + $bot->sendWithKeyboard( + $this->chatId, + "π Tienes una compra pendiente: *{$pp['servicio']}* β \$" . number_format($pp['valor']) . "\nPresiona para completarla:", + [[['text' => "Comprar {$pp['servicio']}", 'callback_data' => 'buy_start|' . $pp['tarifa_id']]]] + ); + } elseif ($pp && ($pp['type'] ?? '') === 'promo') { + $bot->sendWithKeyboard( + $this->chatId, + "π Tienes una compra pendiente: *{$pp['nombre']}* β \$" . number_format($pp['valor']) . "\nPresiona para completarla:", + [[['text' => "Comprar {$pp['nombre']}", 'callback_data' => 'promo_saldo|' . $pp['promo_id']]]] + ); + } else { + $bot->sendWithKeyboard($this->chatId, "ΒΏEn quΓ© mΓ‘s te puedo ayudar?", [ + [ + ['text' => 'πΊ Ver Servicios', 'callback_data' => 'svc_list'], + ['text' => 'π Promociones', 'callback_data' => 'promo_list'], + ], + [ + ['text' => 'π Mis Credenciales', 'callback_data' => 'creds'], + ['text' => 'π Historial', 'callback_data' => 'history'], + ], + ]); + } + } +} diff --git a/app/Models/SolicitudRecarga.php b/app/Models/SolicitudRecarga.php index 41374b6..cce2fa2 100644 --- a/app/Models/SolicitudRecarga.php +++ b/app/Models/SolicitudRecarga.php @@ -33,7 +33,7 @@ class SolicitudRecarga extends Model { return static::where('user_id', $userId) ->where('estado', 'pendiente') - ->where('created_at', '>=', now()->subMinutes(30)) + ->where('created_at', '>=', now()->startOfDay()) ->latest('id') ->first(); } diff --git a/app/Services/PagoValidadorService.php b/app/Services/PagoValidadorService.php index d334aa5..0add9b5 100755 --- a/app/Services/PagoValidadorService.php +++ b/app/Services/PagoValidadorService.php @@ -9,6 +9,11 @@ use Illuminate\Support\Facades\Log; class PagoValidadorService { + /** + * Valida un pago con bΓΊsqueda IMAP progresiva: 30 min β 2 h β todo el dΓa. + * Retorna en cuanto encuentra coincidencia; solo pasa a la siguiente ventana + * si la anterior no devuelve ningΓΊn correo o no hay coincidencia. + */ public function validar(array $datosPago, ?int $usuarioId = null, ?string $nombreUsuario = null, string $canal = 'telegram'): array { $host = WhatsappSystemConfig::get('correo_imap_host', ''); @@ -18,18 +23,52 @@ class PagoValidadorService } try { - $correos = $this->fetchCorreos(); + $correosDia = $this->fetchCorreosDia(); } catch (\Throwable $e) { Log::warning('[PagoValidador] Error IMAP: ' . $e->getMessage()); return ['estado' => 'no_encontrado', 'correo' => null, 'motivo' => 'error_imap', 'debug' => $e->getMessage()]; } - Log::info('[PagoValidador] Correos obtenidos: ' . count($correos)); + Log::info('[PagoValidador] Correos obtenidos del dΓa: ' . count($correosDia)); - if (empty($correos)) { + if (empty($correosDia)) { return ['estado' => 'no_encontrado', 'correo' => null, 'motivo' => 'sin_correos']; } + // Ventanas progresivas: primero los mΓ‘s recientes, luego ampliar + $ventanas = [30, 120, 0]; // minutos; 0 = sin filtro (todo el dΓa) + + foreach ($ventanas as $minutos) { + $subset = $minutos > 0 + ? array_values(array_filter($correosDia, fn ($c) => $this->dentroDeVentana($c, $minutos))) + : $correosDia; + + if (empty($subset)) { + Log::info("[PagoValidador] Ventana {$minutos}min: sin correos, ampliando..."); + continue; + } + + Log::info("[PagoValidador] Ventana {$minutos}min: revisando " . count($subset) . " correos"); + + $resultado = $this->buscarEnCorreos($subset, $datosPago, $usuarioId, $nombreUsuario, $canal); + + // Confirmado o ya_usado β retornar inmediatamente + if (in_array($resultado['estado'], ['confirmado', 'ya_usado'])) { + return $resultado; + } + + // sin_coincidencia β intentar ventana mΓ‘s amplia + } + + return ['estado' => 'no_encontrado', 'correo' => null, 'motivo' => 'sin_coincidencia']; + } + + // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + // LΓ³gica de matching (extraΓda del validar() original) + // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + + private function buscarEnCorreos(array $correos, array $datosPago, ?int $usuarioId, ?string $nombreUsuario, string $canal): array + { $valorIA = (int) ($datosPago['valor'] ?? 0); $tolerancia = (int) ChatConfig::get('validacion_monto_tolerancia', '0'); $horaIA = $datosPago['hora'] ?? ''; @@ -38,14 +77,14 @@ class PagoValidadorService Log::info('[PagoValidador] Buscando: valor=' . $valorIA . ' fecha=' . $fechaIA . ' hora=' . $horaIA . ' llave=' . $llaveIA); - $hayYaUsado = false; // email que coincide en todo pero ya fue usado + $hayYaUsado = false; foreach ($correos as $i => $correo) { - $fuera = $correo['fuera_de_ventana'] ?? false; - $from = $correo['from'] ?? ''; - $subj = $correo['subject'] ?? ''; - $date = $correo['date'] ?? ''; - $body = $correo['body'] ?? ''; + $fuera = $correo['fuera_de_ventana'] ?? false; + $from = $correo['from'] ?? ''; + $subj = $correo['subject'] ?? ''; + $date = $correo['date'] ?? ''; + $body = $correo['body'] ?? ''; Log::info("[PagoValidador] Correo #{$i}: from={$from} | subj={$subj} | date={$date} | fuera_ventana=" . ($fuera ? 'SI' : 'NO') . " | body(200)=" . substr($body, 0, 200)); @@ -59,9 +98,8 @@ class PagoValidadorService Log::info("[PagoValidador] Correo #{$i} parseado: " . json_encode($datosCorreo)); // ββ 1. Valor ββββββββββββββββββββββββββββββββββββββββββββ - // Normalizar: "1,000.00" β 1000 (strip decimals first, then non-digits) $valorRaw = (string) ($datosCorreo['valor'] ?? '0'); - $valorRaw = preg_replace('/[.,]\d{1,2}$/', '', $valorRaw); // quitar decimales + $valorRaw = preg_replace('/[.,]\d{1,2}$/', '', $valorRaw); $valorCorreo = (int) preg_replace('/[^0-9]/', '', $valorRaw); if ($valorIA <= 0 || $valorCorreo <= 0) { @@ -74,7 +112,7 @@ class PagoValidadorService continue; } - // ββ 2. Fecha (mismo dΓa) β solo si ambos tienen fecha ββ + // ββ 2. Fecha (mismo dΓa) βββββββββββββββββββββββββββββββββ if ($fechaIA && ($datosCorreo['fecha'] ?? '')) { if (! $this->mismoDia($fechaIA, $datosCorreo['fecha'])) { Log::info("[PagoValidador] Correo #{$i}: fecha no coincide (ia={$fechaIA} correo={$datosCorreo['fecha']})"); @@ -82,8 +120,7 @@ class PagoValidadorService } } - // ββ 3. Hora exacta β el timestamp del comprobante y del correo deben coincidir - // El correo llega tarde al inbox pero su contenido registra la hora exacta de la transacciΓ³n. + // ββ 3. Hora exacta βββββββββββββββββββββββββββββββββββββββ if ($horaIA && ($datosCorreo['hora'] ?? '')) { if (! $this->horaProxima($horaIA, $datosCorreo['hora'], 0)) { Log::info("[PagoValidador] Correo #{$i}: hora no coincide (ia={$horaIA} correo={$datosCorreo['hora']})"); @@ -91,7 +128,7 @@ class PagoValidadorService } } - // ββ 4. Llave Bancolombia (si la foto la tiene) ββββββββββ + // ββ 4. Llave Bancolombia βββββββββββββββββββββββββββββββββ if ($llaveIA) { $llaveCorreo = strtolower(preg_replace('/\s+/', '', $datosCorreo['llave'] ?? '')); if ($llaveCorreo && $llaveIA !== $llaveCorreo) { @@ -100,23 +137,21 @@ class PagoValidadorService } } - // ββ 5. Hash ΓΊnico (anti-doble-uso) β solo tras verificar fecha/hora/llave ββ - // Si el email coincide en todos los campos pero ya fue usado β es el mismo comprobante + // ββ 5. Hash anti-duplicado βββββββββββββββββββββββββββββββ $emailHash = sha1($body . $date); try { if (PagoConfirmado::yaUsado($emailHash)) { - Log::info("[PagoValidador] Correo #{$i}: ya usado (hash={$emailHash}), buscando siguiente"); + Log::info("[PagoValidador] Correo #{$i}: ya usado (hash={$emailHash})"); $hayYaUsado = true; - continue; // seguir buscando: otro usuario puede tener un email fresco del mismo monto + continue; } } catch (\Throwable $e) { - // Falla cerrado: si no se puede verificar el anti-duplicado, no confirmar - Log::error('[PagoValidador] ERROR tabla pagos_confirmados (ejecutar migrate): ' . $e->getMessage()); + Log::error('[PagoValidador] ERROR tabla pagos_confirmados: ' . $e->getMessage()); return ['estado' => 'no_encontrado', 'correo' => null, 'motivo' => 'error_sistema']; } - // ββ 6. Remitente (bloquea si el correo trae el campo y no coincide) ββ + // ββ 6. Remitente βββββββββββββββββββββββββββββββββββββββββ $remitenteCorreo = $datosCorreo['remitente'] ?? ''; if ($remitenteCorreo && $nombreUsuario) { if (! $this->remitenteCoincide($remitenteCorreo, $nombreUsuario)) { @@ -136,15 +171,11 @@ class PagoValidadorService 'referencia' => $datosPago['referencia'] ?? null, ]); } catch (\Throwable $e) { - // Falla cerrado: si no se puede registrar el hash, no confirmar - Log::error('[PagoValidador] ERROR al marcar hash (ejecutar migrate): ' . $e->getMessage()); + Log::error('[PagoValidador] ERROR al marcar hash: ' . $e->getMessage()); return ['estado' => 'no_encontrado', 'correo' => null, 'motivo' => 'error_sistema']; } - return [ - 'estado' => 'confirmado', - 'correo' => $datosCorreo, - ]; + return ['estado' => 'confirmado', 'correo' => $datosCorreo]; } if ($hayYaUsado) { @@ -152,11 +183,42 @@ class PagoValidadorService return ['estado' => 'ya_usado', 'correo' => null, 'motivo' => 'correo_ya_aplicado']; } - Log::info('[PagoValidador] NingΓΊn correo coincidiΓ³ con los criterios'); return ['estado' => 'no_encontrado', 'correo' => null, 'motivo' => 'sin_coincidencia', 'emails_revisados' => count($correos)]; } - // ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + // Helpers + // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + + /** + * Obtiene todos los correos del dΓa actual (desde medianoche) con un lΓmite alto. + * Se hace una sola conexiΓ³n IMAP; el filtrado progresivo se hace en memoria. + */ + private function fetchCorreosDia(): array + { + // Minutos desde medianoche + 60 de buffer para zona horaria + $minutesDesdeMedianoche = (int) ceil(now()->diffInMinutes(now()->copy()->startOfDay())) + 60; + + $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: 200, minutesBack: $minutesDesdeMedianoche); + } + + private function dentroDeVentana(array $correo, int $minutos): bool + { + $ts = ($correo['date'] ?? '') ? @strtotime($correo['date']) : false; + if ($ts === false) { + return true; // si no hay fecha, incluir por defecto + } + return $ts >= time() - ($minutos * 60); + } private function mismoDia(string $fechaA, string $fechaB): bool { @@ -205,20 +267,4 @@ class PagoValidadorService } return $coincidencias >= 2; } - - private function fetchCorreos(): array - { - $minutos = (int) WhatsappSystemConfig::get('correo_imap_minutos', '120'); - - $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: 30, minutesBack: $minutos); - } } diff --git a/app/Services/TelegramBotService.php b/app/Services/TelegramBotService.php index 724b25a..1da1f35 100755 --- a/app/Services/TelegramBotService.php +++ b/app/Services/TelegramBotService.php @@ -31,6 +31,7 @@ use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Str; +use App\Jobs\ValidarPagoTelegramJob; use App\Services\GeminiAgentService; class TelegramBotService @@ -136,15 +137,12 @@ class TelegramBotService 'svc_view' => $this->viewService($chatId, (int) ($parts[1] ?? 0)), 'buy_start' => $this->startPurchase($chatId, (int) ($parts[1] ?? 0)), 'buy_saldo' => $this->payWithSaldo($chatId), - 'buy_mp' => $this->payWithMP($chatId), 'promo_list' => $this->listPromos($chatId), 'promo_view' => $this->viewPromo($chatId, (int) ($parts[1] ?? 0)), 'promo_saldo' => $this->buyPromoSaldo($chatId, (int) ($parts[1] ?? 0)), - 'promo_mp' => $this->buyPromoMP($chatId, (int) ($parts[1] ?? 0)), 'rec_init' => $this->showRechargeAmounts($chatId), 'rec_custom' => $this->askCustomAmount($chatId), 'rec_amount' => $this->chooseRechargeMethod($chatId, (int) ($parts[1] ?? 0)), - 'rec_mp' => $this->rechargeMP($chatId, (int) ($parts[1] ?? 0)), 'rec_breb' => $this->askRemitenteNombre($chatId, (int) ($parts[1] ?? 0)), 'breb_nom' => $this->seleccionarNombreRemitente($chatId, (string) ($parts[1] ?? 'nuevo')), 'creds' => $this->showCredentials($chatId), @@ -232,15 +230,18 @@ class TelegramBotService ]); } elseif ($motivo === 'sin_coincidencia' || $motivo === 'sin_correos') { + // Guardar para reintento manual (por si el job agota sus intentos) $state['data']['pago_pendiente'] = $datosPago; $this->setState($chatId, $state); - $this->sendWithKeyboard($chatId, - "β³ *Por ahora no encontramos tu pago de confirmaciΓ³n.*\n_Puede demorar entre 1 y 2 minutos en llegar. Espera un momento y presiona Reintentar._", - [ - [['text' => 'π Reintentar', 'callback_data' => 'pay_retry']], - [['text' => 'π MenΓΊ principal', 'callback_data' => 'menu']], - ] + + $this->send($chatId, + "β³ *Estamos validando tu pago*, esto puede demorar unos minutos.\n" + . "Cuando terminemos te confirmaremos por este medio. Espera un momento." ); + + // Lanzar job que reintenta cada 20s hasta 9 veces (β3 min) + ValidarPagoTelegramJob::dispatch($chatId, $datosPago, 1) + ->delay(now()->addSeconds(20)); } else { $motivoTexto = match ($motivo) { 'correo_deshabilitado' => 'π§ VerificaciΓ³n por correo no configurada', @@ -304,7 +305,7 @@ class TelegramBotService } } - private function autoAplicarRecarga(string $chatId, array $state, SolicitudRecarga $solicitud, int $monto): void + public function autoAplicarRecarga(string $chatId, array $state, SolicitudRecarga $solicitud, int $monto): void { $usuarioId = $state['user_id']; $user = User::with('saldo')->find($usuarioId); @@ -323,14 +324,32 @@ class TelegramBotService $solicitud->confirmar(); - unset($state['data']['pago_pendiente'], $state['data']['solicitud_recarga_id']); + $pp = $state['data']['pending_purchase'] ?? null; + unset($state['data']['pago_pendiente'], $state['data']['solicitud_recarga_id'], $state['data']['pending_purchase']); $this->setState($chatId, $state); $this->send($chatId, "β *Β‘Tu recarga de \$" . number_format($monto) . " fue aplicada exitosamente!*\n" . "Tu nuevo saldo es *\$" . number_format($nuevoSaldo) . "*" ); - $this->showMainMenu($chatId); + + if ($pp && ($pp['type'] ?? '') === 'tarifa') { + // Configurar estado para la compra y ejecutarla directamente + $state = $this->getState($chatId); + $state['data'] = [ + 'flow' => 'compra', + 'tarifa_id' => $pp['tarifa_id'], + 'valor' => $pp['valor'], + 'servicio' => $pp['servicio'], + 'servicio_id' => $pp['servicio_id'], + ]; + $this->setState($chatId, $state); + $this->payWithSaldo($chatId); + } elseif ($pp && ($pp['type'] ?? '') === 'promo') { + $this->buyPromoSaldo($chatId, $pp['promo_id']); + } else { + $this->showMainMenu($chatId); + } } public function handleVoice(string $chatId, string $fileId, string $nombre = '', int $duracionSegundos = 0): void @@ -803,8 +822,19 @@ class TelegramBotService ); } else { $falta = $valor - $saldo; - $buttons[] = [['text' => 'π° Recargar saldo', 'callback_data' => 'rec_init']]; - $buttons[] = [['text' => 'π Ver planes', 'callback_data' => 'svc_view|' . $tarifa->servicio_id]]; + + // Guardar compra pendiente para ejecutarla automΓ‘ticamente tras recargar + $state['data']['pending_purchase'] = [ + 'type' => 'tarifa', + 'tarifa_id' => $tarifaId, + 'valor' => $valor, + 'servicio' => $tarifa->servicio->nombre, + 'servicio_id' => $tarifa->servicio_id, + ]; + $this->setState($chatId, $state); + + $buttons[] = [['text' => "π° Recargar \$" . number_format($falta) . " para comprar {$tarifa->servicio->nombre}", 'callback_data' => 'rec_init']]; + $buttons[] = [['text' => 'π Ver planes', 'callback_data' => 'svc_view|' . $tarifa->servicio_id]]; $this->sendWithKeyboard( $chatId, @@ -884,35 +914,6 @@ class TelegramBotService $this->showMainMenu($chatId); } - private function payWithMP(string $chatId): void - { - $state = $this->getState($chatId); - $data = $state['data'] ?? []; - - if (($data['flow'] ?? '') !== 'compra') { - $this->showMainMenu($chatId); - return; - } - - $valor = (int) ($data['valor'] ?? 0); - $servicio = $data['servicio'] ?? 'Plan streaming'; - $ref = 'CHAT-' . strtoupper(Str::random(12)); - $url = $this->createMPPreference($servicio, $valor, $ref); - - if (! $url) { - $this->send($chatId, "No se pudo generar el link de pago. Intenta de nuevo."); - return; - } - - $this->sendWithKeyboard( - $chatId, - "π¦ *Pago con MercadoPago*\n\n{$servicio} β \$" . number_format($valor) . "\nRef: `{$ref}`\n\n[π Pagar ahora]({$url})\n\nDespuΓ©s de pagar, envΓa la foto del comprobante aquΓ.", - [[['text' => 'π MenΓΊ principal', 'callback_data' => 'menu']]] - ); - - $state['data'] = []; - $this->setState($chatId, $state); - } // βββ Promos βββββββββββββββββββββββββββββββββββββββββββββββ @@ -995,8 +996,19 @@ class TelegramBotService ); } else { $falta = $precio - $saldo; - $buttons[] = [['text' => 'π° Recargar saldo', 'callback_data' => 'rec_init']]; - $buttons[] = [['text' => 'π Promociones', 'callback_data' => 'promo_list']]; + + // Guardar compra pendiente para ejecutarla automΓ‘ticamente tras recargar + $state = $this->getState($chatId); + $state['data']['pending_purchase'] = [ + 'type' => 'promo', + 'promo_id' => $promoId, + 'valor' => $precio, + 'nombre' => $promo->nombre ?? 'PromociΓ³n', + ]; + $this->setState($chatId, $state); + + $buttons[] = [['text' => "π° Recargar \$" . number_format($falta) . " para comprar " . ($promo->nombre ?? 'Promo'), 'callback_data' => 'rec_init']]; + $buttons[] = [['text' => 'π Promociones', 'callback_data' => 'promo_list']]; $this->sendWithKeyboard( $chatId, @@ -1073,38 +1085,6 @@ class TelegramBotService $this->showMainMenu($chatId); } - private function buyPromoMP(string $chatId, int $promoId): void - { - $state = $this->getState($chatId); - $rolId = isset($state['user_id']) - ? (User::find($state['user_id'])?->rol_id ?? Role::where('nombre', 'cliente')->value('id')) - : Role::where('nombre', 'cliente')->value('id'); - - $promo = Promociones::with(['tarifaPromo' => fn ($q) => $q->where('rol_id', $rolId)])->find($promoId); - if (! $promo) { - return; - } - - $upric = isset($state['user_id']) - ? Usuario_promo::where('usuario_id', $state['user_id'])->where('promocion_id', $promoId)->first() - : null; - $tp = $promo->tarifaPromo->first(); - $precio = $upric ? $upric->precio : ($tp ? $tp->precio : $promo->precio); - - $ref = 'PROMO-' . strtoupper(Str::random(10)); - $url = $this->createMPPreference($promo->nombre ?? 'Promocion streaming', (int) $precio, $ref); - - if (! $url) { - $this->send($chatId, "No se pudo generar el link de pago. Intenta de nuevo."); - return; - } - - $this->sendWithKeyboard( - $chatId, - "π¦ *Pago con MercadoPago*\n\n{$promo->nombre} β \$" . number_format($precio) . "\nRef: `{$ref}`\n\n[π Pagar ahora]({$url})\n\nDespuΓ©s de pagar, envΓa la foto del comprobante aquΓ.", - [[['text' => 'π MenΓΊ principal', 'callback_data' => 'menu']]] - ); - } // βββ Recharge βββββββββββββββββββββββββββββββββββββββββββββ @@ -1160,13 +1140,16 @@ class TelegramBotService } $llave = ChatConfig::get('recarga_banco_llave', ''); - $buttons = [ - [['text' => 'π¦ MercadoPago', 'callback_data' => 'rec_mp|' . $monto]], - ]; - if ($llave) { - $buttons[] = [['text' => 'π Bre-B', 'callback_data' => 'rec_breb|' . $monto]]; + if (! $llave) { + $this->sendWithKeyboard($chatId, "β οΈ La recarga no estΓ‘ disponible en este momento. Contacta a un asesor.", [ + [['text' => 'π MenΓΊ principal', 'callback_data' => 'menu']], + ]); + return; } - $buttons[] = [['text' => 'π Volver', 'callback_data' => 'rec_init']]; + $buttons = [ + [['text' => 'π Bre-B', 'callback_data' => 'rec_breb|' . $monto]], + [['text' => 'π Volver', 'callback_data' => 'rec_init']], + ]; $this->sendWithKeyboard( $chatId, @@ -1175,41 +1158,6 @@ class TelegramBotService ); } - private function rechargeMP(string $chatId, int $monto): void - { - $state = $this->getState($chatId); - - if ($monto <= 0) { - $this->send($chatId, "Monto invΓ‘lido."); - return; - } - - $ref = 'REC-' . strtoupper(Str::random(12)); - $url = $this->createMPPreference('Recarga de saldo', $monto, $ref); - - if (! $url) { - $this->send($chatId, "No se pudo generar el link de recarga. Intenta de nuevo."); - return; - } - - if ($state['user_id'] ?? null) { - $user = User::with('saldo')->find($state['user_id']); - $saldo = $user->saldo ?? Saldo::create(['usuario_id' => $state['user_id'], 'valor' => 0]); - recarga::create([ - 'monto' => $monto, - 'reference' => $ref, - 'status' => 'pendiente_mp', - 'usuario_id' => $state['user_id'], - 'saldo_id' => $saldo->id, - ]); - } - - $this->sendWithKeyboard( - $chatId, - "π¦ *Recarga con MercadoPago*\n\n\$" . number_format($monto) . "\nRef: `{$ref}`\n\n[π Recargar ahora]({$url})\n\nDespuΓ©s de pagar, envΓa la foto del comprobante aquΓ.", - [[['text' => 'π MenΓΊ principal', 'callback_data' => 'menu']]] - ); - } private function askRemitenteNombre(string $chatId, int $monto): void { @@ -1319,7 +1267,11 @@ class TelegramBotService if ($titular) { $text .= "Titular: *{$titular}*\n"; } - $text .= "\nβ οΈ DespuΓ©s de transferir, envΓa la *foto del comprobante* aquΓ y el sistema lo validarΓ‘ automΓ‘ticamente." + $text .= "\nπ *Al enviar el comprobante, asegΓΊrate de que se vea claramente:*\n" + . "β Tu nombre completo\n" + . "β El valor transferido (\$" . number_format($monto) . ")\n" + . "β La fecha y hora de la transacciΓ³n\n\n" + . "β οΈ DespuΓ©s de transferir, envΓa la *foto del comprobante* aquΓ y el sistema lo validarΓ‘ automΓ‘ticamente." . "\n_Tienes hasta 15 minutos despuΓ©s de la transferencia para enviar el soporte._"; if ($userId = ($this->getState($chatId)['user_id'] ?? null)) { @@ -1679,39 +1631,4 @@ class TelegramBotService return mb_substr($local, 0, $visible) . str_repeat('*', max(0, $len - $visible)) . '@' . $domain; } - private function createMPPreference(string $titulo, int $valor, string $ref): ?string - { - $accessToken = config('services.mercadopago.token'); - if (! $accessToken) { - return null; - } - - try { - $response = Http::withToken($accessToken) - ->timeout(10) - ->post('https://api.mercadopago.com/checkout/preferences', [ - 'items' => [[ - 'title' => $titulo, - 'quantity' => 1, - 'unit_price' => $valor, - 'currency_id' => 'COP', - ]], - 'external_reference' => $ref, - 'back_urls' => [ - 'success' => url('/chat'), - 'failure' => url('/chat'), - 'pending' => url('/chat'), - ], - 'auto_return' => 'approved', - 'payment_methods' => [ - 'excluded_payment_types' => [['id' => 'ticket']], - ], - ]); - - return $response->successful() ? $response->json('init_point') : null; - } catch (\Throwable $e) { - Log::warning('[TelegramMP] Error: ' . $e->getMessage()); - return null; - } - } } diff --git a/resources/views/livewire/chat/public-chat.blade.php b/resources/views/livewire/chat/public-chat.blade.php index b57e005..f39dc34 100755 --- a/resources/views/livewire/chat/public-chat.blade.php +++ b/resources/views/livewire/chat/public-chat.blade.php @@ -236,6 +236,7 @@