1202 lines
46 KiB
PHP
1202 lines
46 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Mail\ChatOtpMail;
|
||
use App\Models\ChatConfig;
|
||
use App\Models\ChatContact;
|
||
use App\Models\ChatConversation;
|
||
use App\Models\ChatMessage;
|
||
use App\Models\Cuentas;
|
||
use App\Models\Historiale;
|
||
use App\Models\Historial_cuenta;
|
||
use App\Models\Preferencial;
|
||
use App\Models\Promociones;
|
||
use App\Models\TarifaPromo;
|
||
use App\Models\Usuario_promo;
|
||
use App\Models\recarga;
|
||
use App\Models\Role;
|
||
use App\Models\Saldo;
|
||
use App\Models\Servicio;
|
||
use App\Models\Tarifas;
|
||
use App\Models\User;
|
||
use Carbon\Carbon;
|
||
use Illuminate\Support\Facades\Cache;
|
||
use Illuminate\Support\Facades\Hash;
|
||
use Illuminate\Support\Facades\Http;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Illuminate\Support\Facades\Mail;
|
||
use Illuminate\Support\Str;
|
||
|
||
class TelegramBotService
|
||
{
|
||
private const STATE_TTL = 7200; // 2 hours
|
||
|
||
private ?int $convId = null;
|
||
|
||
// ─── Entry points ─────────────────────────────────────────
|
||
|
||
public function handleText(string $chatId, string $text, string $nombre = ''): void
|
||
{
|
||
$text = trim($text);
|
||
$state = $this->getState($chatId);
|
||
$this->convId = $state['conv_id'] ?? null;
|
||
|
||
// Save user message if we have a conversation
|
||
if ($this->convId && $text !== '') {
|
||
$conv = ChatConversation::find($this->convId);
|
||
if ($conv) {
|
||
ChatMessage::create([
|
||
'conversation_id' => $this->convId,
|
||
'tipo' => 'usuario',
|
||
'contenido' => $text,
|
||
'leido' => false,
|
||
]);
|
||
$conv->update(['ultimo_mensaje_at' => now()]);
|
||
|
||
if ($conv->estado === 'agente') {
|
||
return; // Human agent is handling this
|
||
}
|
||
}
|
||
}
|
||
|
||
$lower = strtolower($text);
|
||
if (in_array($lower, ['/start', '/menu', 'menu', 'inicio', 'hola'])) {
|
||
if ($state['user_id'] ?? null) {
|
||
$this->showMainMenu($chatId);
|
||
} else {
|
||
$this->askEmail($chatId);
|
||
}
|
||
return;
|
||
}
|
||
|
||
$step = $state['step'] ?? 'await_email';
|
||
|
||
match ($step) {
|
||
'await_email' => $this->processEmail($chatId, $text, $nombre),
|
||
'await_otp' => $this->processOtp($chatId, $text),
|
||
'await_nombre' => $this->processNombre($chatId, $text),
|
||
'await_celular' => $this->processCelular($chatId, $text),
|
||
'await_amount' => $this->processCustomAmount($chatId, $text),
|
||
default => $this->handleFreeText($chatId, $state),
|
||
};
|
||
}
|
||
|
||
public function handleCallback(string $chatId, string $callbackId, string $data): void
|
||
{
|
||
$this->answerCallback($callbackId);
|
||
|
||
$state = $this->getState($chatId);
|
||
$this->convId = $state['conv_id'] ?? null;
|
||
|
||
if (! ($state['user_id'] ?? null)) {
|
||
$this->askEmail($chatId);
|
||
return;
|
||
}
|
||
|
||
$parts = explode('|', $data, 3);
|
||
$action = $parts[0];
|
||
|
||
match ($action) {
|
||
'menu' => $this->showMainMenu($chatId),
|
||
'svc_list' => $this->listServices($chatId),
|
||
'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->rechargeBreb($chatId, (int) ($parts[1] ?? 0)),
|
||
'creds' => $this->showCredentials($chatId),
|
||
'history' => $this->showHistory($chatId),
|
||
'profile' => $this->showProfile($chatId),
|
||
'agent' => $this->transferToAgent($chatId),
|
||
'pay_apply' => $this->applyPayment($chatId, (int) ($parts[1] ?? 0), $parts[2] ?? null),
|
||
default => $this->showMainMenu($chatId),
|
||
};
|
||
}
|
||
|
||
public function handlePhoto(string $chatId, string $fileId): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
$this->convId = $state['conv_id'] ?? null;
|
||
|
||
if (! ($state['user_id'] ?? null)) {
|
||
$this->send($chatId, "Para enviar un comprobante primero debes identificarte. Escribe tu correo.");
|
||
return;
|
||
}
|
||
|
||
if (ChatConfig::get('validacion_foto_habilitada', '0') !== '1') {
|
||
$this->send($chatId, "El análisis de comprobantes no está habilitado. Contacta a un asesor.");
|
||
return;
|
||
}
|
||
|
||
$this->send($chatId, "⏳ Analizando tu comprobante...");
|
||
|
||
try {
|
||
$imageData = $this->downloadTelegramFile($fileId);
|
||
if (! $imageData) {
|
||
$this->send($chatId, "No pude descargar la imagen. Intenta de nuevo.");
|
||
return;
|
||
}
|
||
|
||
$base64 = base64_encode($imageData['content']);
|
||
$mime = $imageData['mime'];
|
||
$datosPago = app(GeminiVisionService::class)->extraerPago($base64, $mime);
|
||
|
||
if (! $datosPago) {
|
||
$this->send($chatId, "No pude leer el comprobante. Intenta con una foto más clara.");
|
||
return;
|
||
}
|
||
|
||
$resultado = app(PagoValidadorService::class)->validar($datosPago);
|
||
$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') {
|
||
$buttons = [
|
||
[['text' => "✅ Aplicar recarga de \$$monto", 'callback_data' => 'pay_apply|' . ($datosPago['valor'] ?? 0) . '|' . $ref]],
|
||
[['text' => '🔙 Menú principal', 'callback_data' => 'menu']],
|
||
];
|
||
} else {
|
||
$buttons = [
|
||
[['text' => '🧑 Hablar con asesor', 'callback_data' => 'agent']],
|
||
[['text' => '🔙 Menú principal', 'callback_data' => 'menu']],
|
||
];
|
||
}
|
||
|
||
$this->sendWithKeyboard($chatId, $texto, $buttons);
|
||
} catch (\Throwable $e) {
|
||
Log::warning('[TelegramBot] Error procesando foto: ' . $e->getMessage());
|
||
$this->send($chatId, "Error analizando el comprobante. Por favor intenta de nuevo.");
|
||
}
|
||
}
|
||
|
||
// ─── Auth flow ────────────────────────────────────────────
|
||
|
||
private function askEmail(string $chatId): void
|
||
{
|
||
$this->setState($chatId, ['step' => 'await_email', 'data' => []]);
|
||
$this->send($chatId, "👋 Hola! Para continuar escribe tu correo electrónico registrado:");
|
||
}
|
||
|
||
private function processEmail(string $chatId, string $email, string $nombre): void
|
||
{
|
||
if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||
$this->send($chatId, "❌ Eso no parece un correo válido. Escríbelo de nuevo:");
|
||
return;
|
||
}
|
||
|
||
$user = User::where('email', $email)->first();
|
||
|
||
if (! $user) {
|
||
$this->setState($chatId, [
|
||
'step' => 'await_nombre',
|
||
'data' => ['email' => $email, 'nombre_tg' => $nombre],
|
||
]);
|
||
$this->send($chatId,
|
||
"No encontré una cuenta con ese correo.\n\n" .
|
||
"¿Deseas crear una cuenta? Escribe tu *nombre completo* o escribe /cancelar para intentar con otro correo."
|
||
);
|
||
return;
|
||
}
|
||
|
||
$contact = ChatContact::where('canal', 'telegram')->where('canal_id', $chatId)->first();
|
||
if ($contact) {
|
||
$contact->update(['user_id' => $user->id, 'nombre' => $user->name]);
|
||
} else {
|
||
$contact = ChatContact::create([
|
||
'canal' => 'telegram',
|
||
'canal_id' => $chatId,
|
||
'nombre' => $user->name,
|
||
'user_id' => $user->id,
|
||
]);
|
||
}
|
||
|
||
$this->sendOtp($chatId, $contact, $user);
|
||
}
|
||
|
||
private function sendOtp(string $chatId, ChatContact $contact, User $user): void
|
||
{
|
||
$codigo = (string) random_int(100000, 999999);
|
||
Cache::put("chat_otp_{$contact->id}", $codigo, now()->addMinutes(10));
|
||
|
||
try {
|
||
Mail::to($user->email)->send(new ChatOtpMail($codigo, $user->name));
|
||
} catch (\Throwable $e) {
|
||
Log::warning('[TelegramOTP] Error enviando correo: ' . $e->getMessage());
|
||
}
|
||
|
||
$this->setState($chatId, [
|
||
'step' => 'await_otp',
|
||
'contact_id' => $contact->id,
|
||
'data' => [],
|
||
]);
|
||
|
||
$masked = $this->maskEmail($user->email);
|
||
$this->send($chatId, "✉️ Te enviamos un código a *{$masked}*.\n\nEscribe el código de 6 dígitos:");
|
||
}
|
||
|
||
private function processOtp(string $chatId, string $text): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
$contactId = $state['contact_id'] ?? null;
|
||
|
||
if (! $contactId || ! preg_match('/^\d{6}$/', $text)) {
|
||
$this->send($chatId, "El código debe tener 6 dígitos. Intenta de nuevo:");
|
||
return;
|
||
}
|
||
|
||
$saved = Cache::get("chat_otp_{$contactId}");
|
||
|
||
if (! $saved) {
|
||
$this->send($chatId, "El código expiró. Escribe tu correo de nuevo:");
|
||
$this->setState($chatId, ['step' => 'await_email', 'data' => []]);
|
||
return;
|
||
}
|
||
|
||
if ($text !== $saved) {
|
||
$this->send($chatId, "❌ Código incorrecto. Intenta de nuevo:");
|
||
return;
|
||
}
|
||
|
||
Cache::forget("chat_otp_{$contactId}");
|
||
|
||
$contact = ChatContact::find($contactId);
|
||
$user = User::with('saldo')->find($contact->user_id);
|
||
$this->openSession($chatId, $contact, $user);
|
||
}
|
||
|
||
// ─── Registration flow ────────────────────────────────────
|
||
|
||
private function processNombre(string $chatId, string $text): void
|
||
{
|
||
if (strtolower($text) === '/cancelar') {
|
||
$this->setState($chatId, ['step' => 'await_email', 'data' => []]);
|
||
$this->send($chatId, "De acuerdo. Escribe tu correo electrónico:");
|
||
return;
|
||
}
|
||
|
||
$state = $this->getState($chatId);
|
||
$state['data']['nombre'] = $text;
|
||
$state['step'] = 'await_celular';
|
||
$this->setState($chatId, $state);
|
||
$this->send($chatId, "Tu número de celular (o escribe /omitir para saltarlo):");
|
||
}
|
||
|
||
private function processCelular(string $chatId, string $text): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
$state['data']['celular'] = strtolower($text) === '/omitir' ? null : $text;
|
||
|
||
$email = $state['data']['email'] ?? null;
|
||
$nombre = $state['data']['nombre'] ?? null;
|
||
|
||
if (! $email || ! $nombre) {
|
||
$this->send($chatId, "Ocurrió un error. Escribe /start para comenzar de nuevo.");
|
||
$this->setState($chatId, ['step' => 'await_email', 'data' => []]);
|
||
return;
|
||
}
|
||
|
||
$rolCliente = Role::where('nombre', 'cliente')->first();
|
||
$user = User::create([
|
||
'name' => $nombre,
|
||
'email' => $email,
|
||
'password' => Hash::make(Str::random(16)),
|
||
'cedula' => $state['data']['cedula'] ?? null,
|
||
'celular' => $state['data']['celular'] ?? null,
|
||
'estado' => 'activo',
|
||
'rol_id' => $rolCliente?->id,
|
||
]);
|
||
|
||
$contact = ChatContact::where('canal', 'telegram')->where('canal_id', $chatId)->first();
|
||
if ($contact) {
|
||
$contact->update(['user_id' => $user->id, 'nombre' => $user->name]);
|
||
} else {
|
||
$contact = ChatContact::create([
|
||
'canal' => 'telegram',
|
||
'canal_id' => $chatId,
|
||
'nombre' => $user->name,
|
||
'user_id' => $user->id,
|
||
]);
|
||
}
|
||
|
||
$this->sendOtp($chatId, $contact, $user);
|
||
}
|
||
|
||
// ─── Open session ─────────────────────────────────────────
|
||
|
||
private function openSession(string $chatId, ChatContact $contact, User $user): void
|
||
{
|
||
$conv = $contact->activeConversation();
|
||
if (! $conv) {
|
||
$conv = ChatConversation::create([
|
||
'contact_id' => $contact->id,
|
||
'canal' => 'telegram',
|
||
'estado' => 'bot',
|
||
'ultimo_mensaje_at' => now(),
|
||
]);
|
||
}
|
||
|
||
$this->convId = $conv->id;
|
||
|
||
$this->setState($chatId, [
|
||
'step' => 'menu',
|
||
'user_id' => $user->id,
|
||
'contact_id' => $contact->id,
|
||
'conv_id' => $conv->id,
|
||
'data' => [],
|
||
]);
|
||
|
||
$saludo = "¡Bienvenido, *{$user->name}*! 🎉\nSaldo disponible: *\$" . number_format($user->saldo?->valor ?? 0) . "*";
|
||
$this->send($chatId, $saludo);
|
||
$this->showMainMenu($chatId);
|
||
}
|
||
|
||
// ─── Main menu ────────────────────────────────────────────
|
||
|
||
private function showMainMenu(string $chatId): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
$state['step'] = 'menu';
|
||
$state['data'] = [];
|
||
$this->setState($chatId, $state);
|
||
|
||
$this->sendWithKeyboard($chatId, "¿En qué te puedo ayudar?", [
|
||
[
|
||
['text' => '📺 Ver Servicios', 'callback_data' => 'svc_list'],
|
||
['text' => '🎁 Promociones', 'callback_data' => 'promo_list'],
|
||
],
|
||
[
|
||
['text' => '💰 Recargar Saldo', 'callback_data' => 'rec_init'],
|
||
['text' => '🔑 Mis Credenciales', 'callback_data' => 'creds'],
|
||
],
|
||
[
|
||
['text' => '📋 Historial', 'callback_data' => 'history'],
|
||
['text' => '👤 Mi Perfil', 'callback_data' => 'profile'],
|
||
],
|
||
[
|
||
['text' => '🧑 Hablar con asesor', 'callback_data' => 'agent'],
|
||
],
|
||
]);
|
||
}
|
||
|
||
// ─── Services ─────────────────────────────────────────────
|
||
|
||
private function listServices(string $chatId): void
|
||
{
|
||
$servicios = Servicio::where('estado', 'activo')->orderBy('ubicacion')->get();
|
||
|
||
if ($servicios->isEmpty()) {
|
||
$this->send($chatId, "No hay servicios disponibles en este momento.");
|
||
$this->showMainMenu($chatId);
|
||
return;
|
||
}
|
||
|
||
$buttons = [];
|
||
foreach ($servicios as $s) {
|
||
$buttons[] = [['text' => $s->nombre, 'callback_data' => 'svc_view|' . $s->id]];
|
||
}
|
||
$buttons[] = [['text' => '🔙 Menú principal', 'callback_data' => 'menu']];
|
||
|
||
$this->sendWithKeyboard($chatId, "📺 *Selecciona el servicio:*", $buttons);
|
||
}
|
||
|
||
private function viewService(string $chatId, int $servicioId): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
$user = User::find($state['user_id']);
|
||
$rolId = $user?->rol_id ?? Role::where('nombre', 'cliente')->value('id');
|
||
$servicio = Servicio::with(['tarifas' => fn ($q) => $q->where('estado', 'activo')->where('rol_id', $rolId)])->find($servicioId);
|
||
|
||
if (! $servicio) {
|
||
$this->send($chatId, "Servicio no encontrado.");
|
||
return;
|
||
}
|
||
|
||
$tarifas = $servicio->tarifas;
|
||
|
||
if ($tarifas->isEmpty()) {
|
||
$this->send($chatId, "No hay planes disponibles para {$servicio->nombre}.");
|
||
$this->listServices($chatId);
|
||
return;
|
||
}
|
||
|
||
$buttons = [];
|
||
foreach ($tarifas as $t) {
|
||
$precio = $this->efectivePrice($t->id, $state['user_id'], $t->valor);
|
||
$label = $servicio->por_tiempo
|
||
? "{$t->dias} días - \$" . number_format($precio)
|
||
: "{$t->pantallas} pantalla(s) / {$t->dias} días - \$" . number_format($precio);
|
||
$buttons[] = [['text' => $label, 'callback_data' => 'buy_start|' . $t->id]];
|
||
}
|
||
$buttons[] = [['text' => '🔙 Ver servicios', 'callback_data' => 'svc_list']];
|
||
|
||
$this->sendWithKeyboard($chatId, "📺 *{$servicio->nombre}* — elige un plan:", $buttons);
|
||
}
|
||
|
||
// ─── Purchase ─────────────────────────────────────────────
|
||
|
||
private function startPurchase(string $chatId, int $tarifaId): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
$tarifa = Tarifas::with('servicio')->find($tarifaId);
|
||
|
||
if (! $tarifa) {
|
||
$this->send($chatId, "Plan no encontrado.");
|
||
return;
|
||
}
|
||
|
||
$disponibles = Cuentas::where('tarifa_id', $tarifaId)->where('estado', 'activo')->count();
|
||
if ($disponibles < 1) {
|
||
$this->send($chatId, "Sin cuentas disponibles para este plan. Intenta con otro.");
|
||
$this->listServices($chatId);
|
||
return;
|
||
}
|
||
|
||
$user = User::with('saldo')->find($state['user_id']);
|
||
$saldo = $user->saldo?->valor ?? 0;
|
||
$valor = $this->efectivePrice($tarifaId, $state['user_id'], $tarifa->valor);
|
||
|
||
$state['data'] = ['flow' => 'compra', 'tarifa_id' => $tarifaId, 'valor' => $valor, 'servicio' => $tarifa->servicio->nombre, 'servicio_id' => $tarifa->servicio_id];
|
||
$this->setState($chatId, $state);
|
||
|
||
$detalle = $tarifa->servicio->por_tiempo
|
||
? "{$tarifa->dias} días"
|
||
: "{$tarifa->pantallas} pantalla(s) / {$tarifa->dias} días";
|
||
|
||
$buttons = [];
|
||
if ($saldo >= $valor) {
|
||
$buttons[] = [['text' => "💳 Pagar con saldo (\$" . number_format($valor) . ")", 'callback_data' => 'buy_saldo']];
|
||
}
|
||
$buttons[] = [['text' => '🏦 MercadoPago', 'callback_data' => 'buy_mp']];
|
||
$buttons[] = [['text' => '🔙 Ver planes', 'callback_data' => 'svc_view|' . $tarifa->servicio_id]];
|
||
|
||
$this->sendWithKeyboard(
|
||
$chatId,
|
||
"*{$tarifa->servicio->nombre}* — {$detalle}\nPrecio: *\$" . number_format($valor) . "*\n\nElige cómo pagar:",
|
||
$buttons
|
||
);
|
||
}
|
||
|
||
private function payWithSaldo(string $chatId): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
$data = $state['data'] ?? [];
|
||
|
||
if (($data['flow'] ?? '') !== 'compra') {
|
||
$this->showMainMenu($chatId);
|
||
return;
|
||
}
|
||
|
||
$tarifa = Tarifas::with('servicio')->find($data['tarifa_id'] ?? 0);
|
||
$user = User::with('saldo')->find($state['user_id']);
|
||
|
||
if (! $tarifa || ! $user) {
|
||
$this->send($chatId, "Ocurrió un error. Intenta de nuevo.");
|
||
return;
|
||
}
|
||
|
||
$saldo = $user->saldo;
|
||
if (! $saldo || $saldo->valor < $tarifa->valor) {
|
||
$this->send($chatId, "Saldo insuficiente (\$" . number_format($saldo?->valor ?? 0) . "). El plan cuesta \$" . number_format($tarifa->valor) . ".");
|
||
return;
|
||
}
|
||
|
||
$cuenta = Cuentas::where('tarifa_id', $tarifa->id)->where('estado', 'activo')->first();
|
||
if (! $cuenta) {
|
||
$this->send($chatId, "No hay cuentas disponibles en este momento. Intenta más tarde.");
|
||
return;
|
||
}
|
||
|
||
$historial = Historiale::create([
|
||
'fecha_inicio' => now(),
|
||
'fecha_final' => now()->addDays($tarifa->dias),
|
||
'valor' => $tarifa->valor,
|
||
'utilidad' => $tarifa->utilidad ?? 0,
|
||
'tipo_pago' => 'saldo',
|
||
'estado' => 'entregado',
|
||
'vendedor_id' => $state['user_id'],
|
||
'tarifa_id' => $tarifa->id,
|
||
'cliente_id' => $state['user_id'],
|
||
'nombre_cliente' => $user->name,
|
||
]);
|
||
|
||
Historial_cuenta::create(['historial_id' => $historial->id, 'cuenta_id' => $cuenta->id]);
|
||
$cuenta->update(['estado' => 'ocupado']);
|
||
$nuevoSaldo = $saldo->valor - $tarifa->valor;
|
||
$saldo->update(['valor' => $nuevoSaldo]);
|
||
|
||
$text = "✅ *¡Compra exitosa!*\n\n";
|
||
$text .= "🎬 *{$tarifa->servicio->nombre}*\n";
|
||
$text .= "📧 Email: `{$cuenta->correo}`\n";
|
||
$text .= "🔑 Password: `{$cuenta->password}`\n";
|
||
if ($tarifa->pantallas) {
|
||
$text .= "📺 Perfil: {$tarifa->pantallas}\n";
|
||
}
|
||
$text .= "📅 Vence: " . now()->addDays($tarifa->dias)->format('d/m/Y') . "\n";
|
||
$text .= "\nSaldo restante: *\$" . number_format($nuevoSaldo) . "*";
|
||
|
||
$this->send($chatId, $text);
|
||
|
||
$state['data'] = [];
|
||
$this->setState($chatId, $state);
|
||
$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 o contacta a un asesor.");
|
||
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 ───────────────────────────────────────────────
|
||
|
||
private function listPromos(string $chatId): 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');
|
||
|
||
$promos = Promociones::where('visible', 'true')
|
||
->where(fn ($q) => $q->whereNull('fecha_limite')->orWhere('fecha_limite', '>=', now()))
|
||
->whereHas('tarifaPromo', fn ($q) => $q->where('rol_id', $rolId))
|
||
->with(['tarifaPromo' => fn ($q) => $q->where('rol_id', $rolId)])
|
||
->get();
|
||
|
||
if ($promos->isEmpty()) {
|
||
$this->send($chatId, "No hay promociones activas en este momento.");
|
||
$this->showMainMenu($chatId);
|
||
return;
|
||
}
|
||
|
||
$text = "🎁 *Promociones disponibles:*\n\n";
|
||
$buttons = [];
|
||
|
||
foreach ($promos as $p) {
|
||
$upric = isset($state['user_id'])
|
||
? Usuario_promo::where('usuario_id', $state['user_id'])->where('promocion_id', $p->id)->first()
|
||
: null;
|
||
$tp = $p->tarifaPromo->first();
|
||
$precio = $upric ? $upric->precio : ($tp ? $tp->precio : $p->precio);
|
||
|
||
$text .= "*{$p->nombre}* — \$" . number_format($precio) . "\n";
|
||
if ($p->descripcion) {
|
||
$text .= "{$p->descripcion}\n";
|
||
}
|
||
if ($p->fecha_limite) {
|
||
$text .= "Hasta: " . Carbon::parse($p->fecha_limite)->format('d/m/Y') . "\n";
|
||
}
|
||
$text .= "\n";
|
||
$buttons[] = [['text' => ($p->nombre ?? 'Promo') . ' — $' . number_format($precio), 'callback_data' => 'promo_view|' . $p->id]];
|
||
}
|
||
$buttons[] = [['text' => '🔙 Menú principal', 'callback_data' => 'menu']];
|
||
|
||
$this->sendWithKeyboard($chatId, $text, $buttons);
|
||
}
|
||
|
||
private function viewPromo(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) {
|
||
$this->send($chatId, "Promoción no encontrada.");
|
||
return;
|
||
}
|
||
|
||
$user = isset($state['user_id']) ? User::with('saldo')->find($state['user_id']) : null;
|
||
$saldo = $user?->saldo?->valor ?? 0;
|
||
|
||
$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);
|
||
|
||
$buttons = [];
|
||
if ($saldo >= $precio) {
|
||
$buttons[] = [['text' => "💳 Pagar con saldo (\$" . number_format($precio) . ")", 'callback_data' => 'promo_saldo|' . $promoId]];
|
||
}
|
||
$buttons[] = [['text' => '🏦 MercadoPago', 'callback_data' => 'promo_mp|' . $promoId]];
|
||
$buttons[] = [['text' => '🔙 Promociones', 'callback_data' => 'promo_list']];
|
||
|
||
$this->sendWithKeyboard(
|
||
$chatId,
|
||
"*{$promo->nombre}*\n\$" . number_format($precio) . "\n\nElige cómo pagar:",
|
||
$buttons
|
||
);
|
||
}
|
||
|
||
private function buyPromoSaldo(string $chatId, int $promoId): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
$user = isset($state['user_id']) ? User::with('saldo')->find($state['user_id']) : null;
|
||
|
||
if (! $user) {
|
||
$this->send($chatId, "No se pudo procesar la compra.");
|
||
return;
|
||
}
|
||
|
||
$rolId = $user->rol_id;
|
||
$promo = Promociones::with(['tarifaPromo' => fn ($q) => $q->where('rol_id', $rolId)])->find($promoId);
|
||
|
||
if (! $promo) {
|
||
$this->send($chatId, "No se pudo procesar la compra.");
|
||
return;
|
||
}
|
||
|
||
$upric = Usuario_promo::where('usuario_id', $user->id)->where('promocion_id', $promoId)->first();
|
||
$tp = $promo->tarifaPromo->first();
|
||
$precio = $upric ? $upric->precio : ($tp ? $tp->precio : $promo->precio);
|
||
$utilidad = $upric ? $upric->utilidad : ($tp ? $tp->utilidad : 0);
|
||
|
||
$saldo = $user->saldo;
|
||
if (! $saldo || $saldo->valor < $precio) {
|
||
$this->send($chatId, "Saldo insuficiente (\$" . number_format($saldo?->valor ?? 0) . "). La promo cuesta \$" . number_format($precio) . ".");
|
||
return;
|
||
}
|
||
|
||
$cuenta = Cuentas::where('promocion_id', $promoId)->where('estado', 'activo')->first();
|
||
if (! $cuenta) {
|
||
$this->send($chatId, "No hay cuentas disponibles para esta promoción.");
|
||
return;
|
||
}
|
||
|
||
$historial = Historiale::create([
|
||
'fecha_inicio' => now(),
|
||
'fecha_final' => now()->addDays(30),
|
||
'valor' => $precio,
|
||
'utilidad' => $utilidad,
|
||
'tipo_pago' => 'saldo',
|
||
'estado' => 'entregado',
|
||
'vendedor_id' => $user->id,
|
||
'promocion_id' => $promoId,
|
||
'cliente_id' => $user->id,
|
||
'nombre_cliente' => $user->name,
|
||
]);
|
||
|
||
Historial_cuenta::create(['historial_id' => $historial->id, 'cuenta_id' => $cuenta->id]);
|
||
$cuenta->update(['estado' => 'ocupado']);
|
||
$nuevoSaldo = $saldo->valor - $precio;
|
||
$saldo->update(['valor' => $nuevoSaldo]);
|
||
|
||
$text = "✅ *¡Promo activada!*\n\n";
|
||
$text .= "🎬 *{$promo->nombre}*\n";
|
||
$text .= "📧 Email: `{$cuenta->correo}`\n";
|
||
$text .= "🔑 Password: `{$cuenta->password}`\n";
|
||
$text .= "📅 Vence: " . now()->addDays(30)->format('d/m/Y') . "\n";
|
||
$text .= "\nSaldo restante: *\$" . number_format($nuevoSaldo) . "*";
|
||
|
||
$this->send($chatId, $text);
|
||
$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 o contacta a un asesor.");
|
||
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 ─────────────────────────────────────────────
|
||
|
||
private function showRechargeAmounts(string $chatId): void
|
||
{
|
||
$buttons = [
|
||
[
|
||
['text' => '$20.000', 'callback_data' => 'rec_amount|20000'],
|
||
['text' => '$50.000', 'callback_data' => 'rec_amount|50000'],
|
||
],
|
||
[
|
||
['text' => '$100.000', 'callback_data' => 'rec_amount|100000'],
|
||
['text' => '$200.000', 'callback_data' => 'rec_amount|200000'],
|
||
],
|
||
[
|
||
['text' => '$500.000', 'callback_data' => 'rec_amount|500000'],
|
||
['text' => '✏️ Otro valor', 'callback_data' => 'rec_custom'],
|
||
],
|
||
[['text' => '🔙 Menú principal', 'callback_data' => 'menu']],
|
||
];
|
||
|
||
$this->sendWithKeyboard($chatId, "💰 *¿Cuánto quieres recargar?*", $buttons);
|
||
}
|
||
|
||
private function askCustomAmount(string $chatId): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
$state['step'] = 'await_amount';
|
||
$this->setState($chatId, $state);
|
||
$this->send($chatId, "Escribe el monto que deseas recargar (mínimo \$1.000):");
|
||
}
|
||
|
||
private function processCustomAmount(string $chatId, string $text): void
|
||
{
|
||
$monto = (int) preg_replace('/\D/', '', $text);
|
||
|
||
if ($monto < 1000) {
|
||
$this->send($chatId, "El monto debe ser al menos \$1.000. Escríbelo de nuevo:");
|
||
return;
|
||
}
|
||
|
||
$state = $this->getState($chatId);
|
||
$state['step'] = 'menu';
|
||
$this->setState($chatId, $state);
|
||
|
||
$this->chooseRechargeMethod($chatId, $monto);
|
||
}
|
||
|
||
private function chooseRechargeMethod(string $chatId, int $monto): void
|
||
{
|
||
if ($monto <= 0) {
|
||
return;
|
||
}
|
||
|
||
$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]];
|
||
}
|
||
$buttons[] = [['text' => '🔙 Volver', 'callback_data' => 'rec_init']];
|
||
|
||
$this->sendWithKeyboard(
|
||
$chatId,
|
||
"Recarga de *\$" . number_format($monto) . "*\n\n¿Cómo quieres pagar?",
|
||
$buttons
|
||
);
|
||
}
|
||
|
||
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 o contacta a un asesor.");
|
||
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 rechargeBreb(string $chatId, int $monto): void
|
||
{
|
||
$banco = ChatConfig::get('recarga_banco_nombre', '');
|
||
$llave = ChatConfig::get('recarga_banco_llave', '');
|
||
$titular = ChatConfig::get('recarga_banco_titular', '');
|
||
|
||
if (! $llave) {
|
||
$this->send($chatId, "El pago por Bre-B no está disponible. Usa MercadoPago o contacta a un asesor.");
|
||
return;
|
||
}
|
||
|
||
$text = "🏧 *Pago por Bre-B*\n\n";
|
||
$text .= "Monto: *\$" . number_format($monto) . "*\n\n";
|
||
if ($banco) {
|
||
$text .= "Banco/Billetera: *{$banco}*\n";
|
||
}
|
||
$text .= "Llave: `{$llave}`\n";
|
||
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.";
|
||
|
||
$this->sendWithKeyboard($chatId, $text, [
|
||
[['text' => '🔙 Menú principal', 'callback_data' => 'menu']],
|
||
]);
|
||
}
|
||
|
||
// ─── Credentials ──────────────────────────────────────────
|
||
|
||
private function showCredentials(string $chatId): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
|
||
$historiales = Historiale::where('cliente_id', $state['user_id'])
|
||
->where('estado', 'entregado')
|
||
->where('fecha_final', '>=', now())
|
||
->with('tarifa.servicio')
|
||
->orderBy('fecha_final', 'desc')
|
||
->limit(10)
|
||
->get();
|
||
|
||
if ($historiales->isEmpty()) {
|
||
$this->send($chatId, "No tienes planes activos en este momento.");
|
||
$this->showMainMenu($chatId);
|
||
return;
|
||
}
|
||
|
||
$this->send($chatId, "🔑 *Tus planes activos:*");
|
||
|
||
foreach ($historiales as $h) {
|
||
foreach ($h->cuentas as $c) {
|
||
$text = "🎬 *" . ($h->tarifa?->servicio?->nombre ?? 'Servicio') . "*\n";
|
||
$text .= "📧 Email: `{$c->correo}`\n";
|
||
$text .= "🔑 Password: `{$c->password}`\n";
|
||
if ($h->tarifa?->pantallas) {
|
||
$text .= "📺 Perfil: {$h->tarifa->pantallas}\n";
|
||
}
|
||
$text .= "📅 Vence: " . Carbon::parse($h->fecha_final)->format('d/m/Y');
|
||
$this->send($chatId, $text);
|
||
}
|
||
}
|
||
|
||
$this->sendWithKeyboard($chatId, "", [[['text' => '🔙 Menú principal', 'callback_data' => 'menu']]]);
|
||
}
|
||
|
||
// ─── History ──────────────────────────────────────────────
|
||
|
||
private function showHistory(string $chatId): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
|
||
$historiales = Historiale::where('cliente_id', $state['user_id'])
|
||
->with('tarifa.servicio')
|
||
->orderBy('created_at', 'desc')
|
||
->limit(10)
|
||
->get();
|
||
|
||
if ($historiales->isEmpty()) {
|
||
$this->send($chatId, "No tienes compras registradas.");
|
||
$this->showMainMenu($chatId);
|
||
return;
|
||
}
|
||
|
||
$text = "📋 *Tus últimas compras:*\n\n";
|
||
foreach ($historiales as $h) {
|
||
$activo = Carbon::parse($h->fecha_final)->gte(now()) ? '✅' : '❌';
|
||
$nombre = $h->tarifa?->servicio?->nombre ?? ($h->promocion_id ? 'Promo' : 'Plan');
|
||
$fecha = Carbon::parse($h->created_at)->format('d/m/Y');
|
||
$text .= "{$activo} {$nombre} — \$" . number_format($h->valor) . " ({$fecha})\n";
|
||
}
|
||
|
||
$this->sendWithKeyboard($chatId, $text, [[['text' => '🔙 Menú principal', 'callback_data' => 'menu']]]);
|
||
}
|
||
|
||
// ─── Profile ──────────────────────────────────────────────
|
||
|
||
private function showProfile(string $chatId): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
$user = User::with('saldo')->find($state['user_id']);
|
||
|
||
$text = "👤 *Tu perfil*\n\n";
|
||
$text .= "Nombre: *{$user->name}*\n";
|
||
$text .= "Email: {$user->email}\n";
|
||
if ($user->celular) {
|
||
$text .= "Celular: {$user->celular}\n";
|
||
}
|
||
$text .= "Saldo: *\$" . number_format($user->saldo?->valor ?? 0) . "*";
|
||
|
||
$this->sendWithKeyboard($chatId, $text, [[['text' => '🔙 Menú principal', 'callback_data' => 'menu']]]);
|
||
}
|
||
|
||
// ─── Agent ────────────────────────────────────────────────
|
||
|
||
private function transferToAgent(string $chatId): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
if ($state['conv_id'] ?? null) {
|
||
ChatConversation::where('id', $state['conv_id'])->update(['estado' => 'agente']);
|
||
}
|
||
$this->send($chatId, ChatConfig::get('mensaje_transferencia', 'Un asesor se comunicará contigo en breve. Por favor espera.'));
|
||
}
|
||
|
||
// ─── Apply validated payment ──────────────────────────────
|
||
|
||
private function applyPayment(string $chatId, int $monto, ?string $ref): void
|
||
{
|
||
$state = $this->getState($chatId);
|
||
|
||
if (! ($state['user_id'] ?? null) || $monto <= 0) {
|
||
$this->send($chatId, "No se pudo aplicar la recarga.");
|
||
return;
|
||
}
|
||
|
||
$user = User::with('saldo')->find($state['user_id']);
|
||
$saldo = $user->saldo ?? Saldo::create(['usuario_id' => $state['user_id'], 'valor' => 0]);
|
||
$nuevoSaldo = $saldo->valor + $monto;
|
||
$saldo->update(['valor' => $nuevoSaldo]);
|
||
|
||
$this->send(
|
||
$chatId,
|
||
"✅ Recarga de *\$" . number_format($monto) . "* aplicada.\nNuevo saldo: *\$" . number_format($nuevoSaldo) . "*"
|
||
);
|
||
$this->showMainMenu($chatId);
|
||
}
|
||
|
||
// ─── Free text fallback ───────────────────────────────────
|
||
|
||
private function handleFreeText(string $chatId, array $state): void
|
||
{
|
||
if (! ($state['user_id'] ?? null)) {
|
||
$this->askEmail($chatId);
|
||
return;
|
||
}
|
||
|
||
if ($state['conv_id'] ?? null) {
|
||
$conv = ChatConversation::find($state['conv_id']);
|
||
if ($conv && $conv->estado === 'agente') {
|
||
return;
|
||
}
|
||
}
|
||
|
||
$this->showMainMenu($chatId);
|
||
}
|
||
|
||
// ─── State management ─────────────────────────────────────
|
||
|
||
private function getState(string $chatId): array
|
||
{
|
||
return Cache::get("tgbot_state_{$chatId}", ['step' => 'await_email', 'data' => []]);
|
||
}
|
||
|
||
private function setState(string $chatId, array $state): void
|
||
{
|
||
Cache::put("tgbot_state_{$chatId}", $state, now()->addSeconds(self::STATE_TTL));
|
||
}
|
||
|
||
// ─── Telegram API ─────────────────────────────────────────
|
||
|
||
public function send(string $chatId, string $text): bool
|
||
{
|
||
if ($this->convId) {
|
||
ChatMessage::create([
|
||
'conversation_id' => $this->convId,
|
||
'tipo' => 'bot',
|
||
'contenido' => $text,
|
||
'leido' => true,
|
||
]);
|
||
}
|
||
|
||
return $this->apiCall('sendMessage', [
|
||
'chat_id' => $chatId,
|
||
'text' => $text,
|
||
'parse_mode' => 'Markdown',
|
||
]);
|
||
}
|
||
|
||
public function sendWithKeyboard(string $chatId, string $text, array $inlineKeyboard): bool
|
||
{
|
||
if ($this->convId) {
|
||
ChatMessage::create([
|
||
'conversation_id' => $this->convId,
|
||
'tipo' => 'bot',
|
||
'contenido' => $text,
|
||
'leido' => true,
|
||
]);
|
||
}
|
||
|
||
return $this->apiCall('sendMessage', [
|
||
'chat_id' => $chatId,
|
||
'text' => $text ?: '', // zero-width space fallback
|
||
'parse_mode' => 'Markdown',
|
||
'reply_markup' => json_encode(['inline_keyboard' => $inlineKeyboard]),
|
||
]);
|
||
}
|
||
|
||
private function answerCallback(string $callbackQueryId): void
|
||
{
|
||
$this->apiCall('answerCallbackQuery', ['callback_query_id' => $callbackQueryId]);
|
||
}
|
||
|
||
private function apiCall(string $method, array $data): bool
|
||
{
|
||
$token = ChatConfig::get('telegram_token');
|
||
if (! $token) {
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
$response = Http::timeout(10)
|
||
->post("https://api.telegram.org/bot{$token}/{$method}", $data);
|
||
|
||
return $response->successful();
|
||
} catch (\Throwable $e) {
|
||
Log::warning("[TelegramBot] apiCall {$method} failed: " . $e->getMessage());
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private function downloadTelegramFile(string $fileId): ?array
|
||
{
|
||
$token = ChatConfig::get('telegram_token');
|
||
if (! $token) {
|
||
return null;
|
||
}
|
||
|
||
try {
|
||
$info = Http::timeout(10)->get("https://api.telegram.org/bot{$token}/getFile", ['file_id' => $fileId]);
|
||
$filePath = $info->json('result.file_path');
|
||
if (! $filePath) {
|
||
return null;
|
||
}
|
||
|
||
$content = Http::timeout(30)->get("https://api.telegram.org/file/bot{$token}/{$filePath}")->body();
|
||
if (! $content) {
|
||
return null;
|
||
}
|
||
|
||
$ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
|
||
$mime = match ($ext) {
|
||
'png' => 'image/png',
|
||
'webp' => 'image/webp',
|
||
default => 'image/jpeg',
|
||
};
|
||
|
||
return ['content' => $content, 'mime' => $mime];
|
||
|
||
} catch (\Throwable $e) {
|
||
Log::warning('[TelegramBot] downloadTelegramFile failed: ' . $e->getMessage());
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// ─── Helpers ──────────────────────────────────────────────
|
||
|
||
private function efectivePrice(int $tarifaId, ?int $userId, float $base): float
|
||
{
|
||
if ($userId) {
|
||
$pref = Preferencial::where('usuario_id', $userId)->where('tarifa_id', $tarifaId)->first();
|
||
if ($pref) {
|
||
return (float) $pref->valor;
|
||
}
|
||
}
|
||
return (float) $base;
|
||
}
|
||
|
||
private function maskEmail(string $email): string
|
||
{
|
||
[$local, $domain] = explode('@', $email, 2);
|
||
$visible = mb_substr($local, 0, min(3, mb_strlen($local)));
|
||
return $visible . str_repeat('*', max(0, mb_strlen($local) - 3)) . '@' . $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;
|
||
}
|
||
}
|
||
}
|