vueloenglobo.mx/app/Http/Controllers/PagesController.php

486 lines
18 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Api\TelegramController;
use App\Http\Controllers\Page\VuelosController;
use Artesaos\SEOTools\Facades\SEOTools;
use DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Request;
use Illuminate\Support\Facades\Validator;
class PagesController extends Controller
{
public $objVueloController;
public $telegramObject;
public function __construct()
{
$this->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<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);
}
}
}