objVueloController = new VuelosController(); $this->telegramObject = new TelegramController(); } //pagina de inicio public function index() { // SEO mejorado para vueloenglobo.mx SEOTools::setTitle('Vuelos en globo - vueloenglobo.mx | Experiencias en Teotihuacán'); SEOTools::setDescription('Reserva vuelos en globo sobre Teotihuacán. Experiencias seguras, guías expertos y las mejores vistas al mejor precio. Reserva online en VueloEnGlobo.mx.'); SEOTools::metatags()->setKeywords(['vuelos en globo', 'vuelo en globo teotihuacan', 'vuelos baratos', 'experiencias en globo', 'reservar vuelo en globo']); SEOTools::setCanonical('https://vueloenglobo.mx/'); SEOTools::opengraph()->addProperty('type', 'website'); //$vuelos = EsConsultasController::getVueloCompleto(); $response = $this->objVueloController->getVuelosCollectionHome(); $data = $response->getData(true); $vuelos = $data['vuelos']; //return $vuelos; return view('template.home', compact('vuelos')); } /** * Método que me carga y agrupa los vuelos disponibles por categoría * @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View */ public function vuelosEnGlobo() { $vuelos = $this->agruparVuelosListado($this->objVueloController->getVuelosCollection()); return view('template.vuelos-en-globo', compact('vuelos')); } /** * Método que agrupar por categoría los vuelos en globo * @param mixed $listadoVuelos * @return array */ private function agruparVuelosListado($listadoVuelos) { $data = $listadoVuelos->getData(true); $agrupados = []; foreach ($data['vuelos'] as $item) { $categoria = $item['categoria']['nombre'] ?? 'Sin categoría'; if (!isset($agrupados[$categoria])) { $agrupados[$categoria] = []; } $agrupados[$categoria][] = $item; } return $agrupados; } public function vuelosEnGloboCopy() { $vuelos = EsConsultasController::getVueloCompleto(); // return $vuelos; return view('template.vuelos-en-globo', compact('vuelos')); } public function vueloSeleccionado(Request $request, $vuelo) { $objClase = new VuelosController(); $response = $objClase->showByPermanentLink($request, $vuelo); $datosVuelo = $response->getData(true); return view('template.vuelo-seleccionado', compact('datosVuelo')); } // public function vueloSeleccionado($vuelo) // { // $datosVuelo = EsConsultasController::getInfoVuelo($vuelo); // return view('template.vuelo-seleccionado', compact('datosVuelo')); // } public function realizaPago($vuelo) { $datosVuelo = EsConsultasController::getInfoVuelo($vuelo); $adicionales = EsConsultasController::obtenerServiciosAdicionales(); //return $adicionales; return view('template.checkout', compact('datosVuelo', 'adicionales')); } //esta pendiente public function vueloSeleccionado2($vuelo) { $datos_vuelo = EsConsultasController::getDatosVuelo($vuelo); if (!$datos_vuelo) { abort(404); } // return $datos_vuelo; return view('template.vuelo-seleccionado', [ 'datos_vuelo' => $datos_vuelo, 'incluye' => json_decode($datos_vuelo->json_incluye ?? '[]', true) ?: [], 'adicionales' => json_decode($datos_vuelo->precios_adicionales ?? '[]', true) ?: [], ]); } // pagina de contacto public function contacto() { return view('template.contacto'); } // pagina de servicios public function servicios() { return view('template.servicios'); } // pagina de nosotros public function nosotros() { return view('template.nosotros'); } // pagina de preguntas frecuentes public function preguntasFrecuentes() { return view('template.preguntasFrecuentes'); } // pagina de terminos y condiciones public function terminosCondiciones() { return view('template.terminosCondiciones'); } public function cotizarVuelo($slug, Request $request) { //obtener el vuelo $vuelo = DB::table('vuelos')->where('id_vuelo', $request->id_vuelo)->first(); $partes = explode(' ', $request->nombre); $nombres = implode(' ', array_slice($partes, 0, -2)); $apellidoPaterno = $partes[count($partes) - 2]; $apellidoMaterno = end($partes); CotizacionController::crearCotizacionYPosibleCompraErp([ 'nombre' => $nombres, 'apellidos' => $apellidoPaterno . ' ' . $apellidoMaterno, 'email' => $request->email, 'telefono' => $request->telefono, 'total_compra' => $vuelo->precio_publico * $request->pasajeros, 'total_a_pagar' => $vuelo->precio_publico * $request->pasajeros, ]); PagesController::sendTelegramMessage('se cotizo un vuelo: ' . $request->nombre_vuelo . ' a nombre de: ' . $request->nombre . '. mas información consultarla en el ERP'); $response = VuelosController::cotizaVuelo($slug, $request); return json_encode(['mesaaje' => 'Se cotizo el vuelo exitosamente']); } private static function sendTelegramMessage($message) { $botToken = '8617998449:AAFK4DNGPV3efRpmcjeMVZhdTUeErGj-ePc'; $chatId = '8446032916'; Http::post("https://api.telegram.org/bot{$botToken}/sendMessage", [ 'chat_id' => $chatId, 'text' => $message, ]); } /** * Método que se muestra al cliente para completar el proceso de su * @param mixed $slug * @param Request $request * @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View */ public function reservarVueloAhora($slug = null, Request $request) { $objClase = new VuelosController(); $response = $objClase->showByPermanentLink($request, $slug); $datosVuelo = $response->getData(true); $adicionales = $this->getServiciosAdicionales(); SEOTools::setTitle($datosVuelo['vuelo']['nombre_vuelo'] . ' | www.vueloenglobo.mx'); SEOTools::setDescription($datosVuelo['vuelo']['descripcion_corta']); SEOTools::metatags()->setKeywords(['vuelos', 'vuelos baratos', 'ofertas']); return view('template.checkout', compact('datosVuelo', 'adicionales')); } /** * Obtiene y transforma los servicios adicionales desde la API externa * * @return array Array de categorías con sus servicios */ private function getServiciosAdicionales(): array { try { $response = Http::get('https://erp.skyballoons.mx/servicios-adicionales-webservice', [ 'comercio' => 'skyballoons', 'key' => 'f093328776ae4cd210dc7b4ee4093e45', ]); if (!$response->successful()) { return []; } $datos = $response->json(); if (!is_array($datos) || empty($datos)) { return []; } // Transformar al formato de categorías (una sola categoría por simplicidad) $servicios = array_map(function ($item) { return [ 'id' => $item['id_servicio_adicional'] ?? null, 'descripcion' => $item['nombre'] ?? 'Servicio', 'precio' => (float) ($item['precio'] ?? 0), 'precio_dolares' => (float) ($item['precio_dolares'] ?? 0), 'imagenes' => $item['galeria'] ?? [], 'observaciones' => $item['descripcion'] ?? '', ]; }, $datos); return [ [ 'nombre_categoria' => 'Servicios adicionales', 'servicios' => array_filter($servicios, fn($s) => !is_null($s['id'])), ], ]; } catch (\Exception $e) { \Log::error('Error al obtener servicios adicionales: ' . $e->getMessage()); return []; } } public function getNombresDeVuelos() { $vuelos = $this->agruparVuelosListado($this->objVueloController->getVuelosCollection()); return collect($vuelos)->map(function ($items) { return collect($items) ->map(function ($item) { return [ 'nombre_vuelo' => $item['vuelo']['nombre_vuelo'] ?? null, 'enlace_permanente' => $item['vuelo']['enlace_permanente'] ?? null, ]; }) ->values(); }); } public function procesarR(Request $request) { try { Log::info('Procesando pago', $request->all()); $payload = [ 'amount' => (float) $request->input('total'), 'currency' => 'MXN', 'description' => $request->input('payment_description', 'Vuelo en Globo'), 'payment_method' => [ 'token' => $request->input('card_token'), ], 'customer' => [ 'email' => $request->input('payment_email'), 'phone' => preg_replace('/[^0-9]/', '', $request->input('payment_phone')), ], ]; $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . config('clip.api_key'), 'Content-Type' => 'application/json', ])->post(config('clip.payments_url'), $payload); \Log::info('Respuesta Clip', ['status' => $response->status(), 'body' => $response->body()]); if ($response->successful()) { $paymentData = $response->json(); $paymentStatus = $paymentData['status'] ?? 'pending'; $paymentMessage = $paymentData['message'] ?? 'Pago procesado exitosamente'; if ($paymentStatus == 'approved') { PagesController::sendTelegramMessage('Se realizó compra de vuelo en globo. ID: ' . ($paymentData['id'] ?? 'N/A')); } return response()->json( [ 'success' => true, 'payment_status' => $paymentStatus, 'payment_message' => $paymentMessage, 'redirect' => '#', // route('gracias') 'data' => $paymentData, ], 200, ); } else { $errorBody = $response->json(); // Motivo de rechazo que viene de Clip (ajusta el campo según la documentación real) $reason = $errorBody['reason'] ?? ($errorBody['error_code']['reason'] ?? ($errorBody['rejection_reason'] ?? '')); // Mapa de motivos de rechazo a mensajes en español $reasonMessages = [ 'not_sufficient_funds' => 'Fondos insuficientes en la tarjeta.', 'Not sufficient funds' => 'Fondos insuficientes en la tarjeta.', 'do_not_honor' => 'El banco emisor rechazó la transacción (Do not honor).', 'Do not honor' => 'El banco emisor rechazó la transacción.', 'restricted_card' => 'La tarjeta está restringida y no puede usarse.', 'Restricted card' => 'Tarjeta restringida.', 'exceeds_withdrawal_amount_limit' => 'La transacción excede el límite de retiro de esta tarjeta.', 'Exceeds withdrawal amount limit' => 'Excede el límite de retiro permitido.', ]; // Si hay un motivo conocido, usamos el mensaje traducido; si no, el mensaje original if ($reason && isset($reasonMessages[$reason])) { $errorMessage = $reasonMessages[$reason]; } else { $errorMessage = $errorBody['detail'] ?? ($errorBody['error_code']['message'] ?? ($errorBody['message'] ?? 'Error al procesar el pago')); } PagesController::sendTelegramMessage('Error en compra: ' . $errorMessage); return response()->json( [ 'success' => false, 'message' => $errorMessage, 'payment_status' => 'rejected', 'code' => $errorBody['error_code'] ?? null, 'reason' => $reason ?: null, ], $response->status(), ); } } catch (\Exception $e) { \Log::error('Error en procesar pago: ' . $e->getMessage(), [ 'trace' => $e->getTraceAsString(), ]); PagesController::sendTelegramMessage('Error en compra: '); return response()->json( [ 'success' => false, 'message' => 'Error interno del servidor. Por favor intenta de nuevo.', 'payment_status' => 'error', ], 500, ); } } public function procesar(Request $request) { // 1. Validación del payload real que envías desde frontend // $validated = $request->validate([ // 'payment_email' => 'required|email', // 'payment_phone' => 'required|string', // 'payment_description' => 'nullable|string|max:255', // 'card_token' => 'required|string', // 'total' => 'required|numeric', // // datos del vuelo / reserva // 'id_vuelo' => 'required|integer', // 'adultos' => 'nullable|integer', // 'ninios' => 'nullable|integer', // 'fecha' => 'nullable|date', // 'cupon' => 'nullable|string', // ]); $validated = $request->all(); // Elimina esta línea y descomenta la validación real cuando estés listo para validar los datos de entrada // 2. API Key $apiKey = config('clip.api_key'); if (empty($apiKey)) { Log::error('Clip API Key no configurada'); return response()->json([ 'success' => false, 'message' => 'Error de configuración del servidor de pagos.', ], 500); } // 3. Payload oficial Clip $payload = [ "amount" => (float) $validated['total'], "currency" => "MXN", "description" => $validated['payment_description'] ?? 'Vuelo en Globo Zero', "payment_method" => [ "token" => $validated['card_token'] ], "customer" => [ "email" => $validated['payment_email'], "phone" => $validated['payment_phone'], ], "metadata" => [ "id_vuelo" => $validated['id_vuelo'], "adultos" => $validated['adultos'] ?? 0, "ninios" => $validated['ninios'] ?? 0, "fecha" => $validated['fecha'], ] ]; try { // 4. Petición a Clip $response = Http::withHeaders([ "Content-Type" => "application/json", "Authorization" => "Bearer " . $apiKey, ])->post("https://api.payclip.com/payments", $payload); $body = $response->json(); Log::info('Respuesta Clip:', [ 'status' => $response->status(), 'body' => $body ]); // 5. Respuesta HTTP inválida if (!$response->successful()) { return response()->json([ 'success' => false, 'message' => $body['message'] ?? 'Error al procesar pago con Clip', 'error' => $body ], $response->status()); } // 6. Estado del pago $status = strtolower($body['status'] ?? ''); if (in_array($status, ['approved', 'paid'])) { // 7. AQUÍ YA PUEDES GUARDAR LA RESERVA // Reserva::create([...]); return response()->json([ 'success' => true, 'payment_id' => $body['id'] ?? null, 'payment_status' => $status, 'message' => 'Pago aprobado correctamente', ]); } if (in_array($status, ['declined'])) { return response()->json([ 'success' => false, 'payment_status' => $status, 'message' => 'Pago rechazado por el banco.', ], 422); } return response()->json([ 'success' => false, 'payment_status' => $status, 'message' => 'Estado de pago: ' . $status, ], 422); } catch (\Exception $e) { Log::error('Error Clip: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'Error de conexión con el proveedor de pagos.', ], 500); } } }