loadConfig(); // LOG DE EMERGENCIA $debugLogPath = __DIR__ . '/data/debug_public.log'; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $input = file_get_contents('php://input'); $jsonData = json_decode((string)$input, true); if ($jsonData && (isset($jsonData['uuid']) || isset($jsonData['eventName']) || isset($jsonData['type']))) { file_put_contents($debugLogPath, "[" . date('Y-m-d H:i:s') . "] Webhook in public.php. Delegating..." . PHP_EOL, FILE_APPEND); $builder = new \DI\ContainerBuilder(); $container = $builder->build(); $plugin = $container->get(\SmsNotifier\Plugin::class); $plugin->run(); exit; } } $ipServer = $config['ipserver'] ?? 'localhost'; $ucrmApiUrl = 'https://' . $ipServer . '/crm/api/v1.0/'; $ucrmPublicUrl = 'https://' . $ipServer . '/crm'; $httpClient = new \GuzzleHttp\Client([ 'base_uri' => $ucrmApiUrl, 'verify' => false, 'timeout' => 15 ]); $ucrmApi = new UcrmApi($httpClient, $config['apitoken'] ?? ''); $paymentIntentService = new PaymentIntentService($ucrmApi, $config['tokenstripe'] ?? ''); // Admins Logic $admins = []; $defaultStripeAdminId = null; try { $adminsRaw = $ucrmApi->get('users/admins'); foreach ($adminsRaw as $admin) { $nombre = trim(($admin['firstName'] ?? '') . ' ' . ($admin['lastName'] ?? '')); $admins[] = ['id' => $admin['id'], 'nombre' => $nombre]; if (strtolower($nombre) === 'stripe' || strtolower($admin['username'] ?? '') === 'stripe') { $defaultStripeAdminId = $admin['id']; } } if ($defaultStripeAdminId === null && !empty($admins)) $defaultStripeAdminId = $admins[0]['id']; } catch (\Exception $e) { } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 0. LOGIN SCREEN SUPPORT & AUTHENTICATION // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Determinar si el usuario necesita login (si no tiene sesión UCRM activa) $security = UcrmSecurity::create(); $currentUser = $security->getUser(); $needsLogin = !$currentUser; $nmsBaseUrl = "https://{$ipServer}/nms/api/v2.1"; // 0a. NMS Login (POST username + password) if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['action']) && $_GET['action'] === 'nms_login') { header('Content-Type: application/json'); $input = json_decode(file_get_contents('php://input'), true); $username = $input['username'] ?? ''; $password = $input['password'] ?? ''; if (!$username || !$password) { http_response_code(400); echo json_encode(['error' => 'Se requieren usuario y contraseña.']); exit; } try { $nmsClient = new \GuzzleHttp\Client(['verify' => false, 'http_errors' => false]); $resp = $nmsClient->post("{$nmsBaseUrl}/user/login", [ 'json' => ['username' => $username, 'password' => $password], 'headers' => ['Content-Type' => 'application/json'], ]); $statusCode = $resp->getStatusCode(); $body = json_decode($resp->getBody()->getContents(), true); $authToken = $resp->getHeaderLine('x-auth-token'); if ($statusCode === 200 && $authToken) { echo json_encode(['success' => true, 'token' => $authToken, 'user' => $body]); } elseif ($statusCode === 201) { http_response_code(201); echo json_encode(['requires2FA' => true, 'twoFactorToken' => $body]); } else { http_response_code($statusCode ?: 401); echo json_encode(['error' => $body['message'] ?? 'Credenciales inválidas.', 'statusCode' => $statusCode]); } } catch (\Exception $e) { http_response_code(500); echo json_encode(['error' => 'Error de conexión con el servidor UISP.']); } exit; } // 0b. NMS TOTP Login if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['action']) && $_GET['action'] === 'nms_login_totp') { header('Content-Type: application/json'); $input = json_decode(file_get_contents('php://input'), true); try { $nmsClient = new \GuzzleHttp\Client(['verify' => false, 'http_errors' => false]); $resp = $nmsClient->post("{$nmsBaseUrl}/user/login/totpauth", [ 'json' => $input, 'headers' => ['Content-Type' => 'application/json'], ]); $statusCode = $resp->getStatusCode(); $body = json_decode($resp->getBody()->getContents(), true); $authToken = $resp->getHeaderLine('x-auth-token'); if ($statusCode === 200 && $authToken) { echo json_encode(['success' => true, 'token' => $authToken, 'user' => $body]); } else { http_response_code($statusCode ?: 401); echo json_encode(['error' => $body['message'] ?? 'Código TOTP inválido.']); } } catch (\Exception $e) { http_response_code(500); echo json_encode(['error' => 'Error de conexión con el servidor UISP.']); } exit; } // 0c. NMS Verify Session if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action']) && $_GET['action'] === 'nms_verify_session') { header('Content-Type: application/json'); $token = $_SERVER['HTTP_X_AUTH_TOKEN'] ?? ''; if (!$token) { http_response_code(401); echo json_encode(['error' => 'No token provided.']); exit; } try { $nmsClient = new \GuzzleHttp\Client(['verify' => false, 'http_errors' => false]); $resp = $nmsClient->get("{$nmsBaseUrl}/user", [ 'headers' => ['x-auth-token' => $token], ]); if ($resp->getStatusCode() === 200) { echo json_encode(['success' => true, 'user' => json_decode($resp->getBody()->getContents(), true)]); } else { http_response_code(401); echo json_encode(['error' => 'Sesión inválida o expirada.']); } } catch (\Exception $e) { http_response_code(500); echo json_encode(['error' => 'Error verificando sesión.']); } exit; } // API HANDLERS if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { if ($_POST['action'] === 'save_installers') { $installersJson = $_POST['installers_data'] ?? ''; if (json_decode($installersJson) !== null) { $configPath = __DIR__ . '/data/config.json'; $currentConfig = json_decode(file_get_contents($configPath), true); $currentConfig['installersDataWhatsApp'] = $installersJson; file_put_contents($configPath, json_encode($currentConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); echo json_encode(['success' => true]); exit; } echo json_encode(['success' => false]); exit; } if ($_POST['action'] === 'resend_payment') { $paymentId = $_POST['paymentId'] ?? null; try { $payment = $ucrmApi->get("payments/$paymentId"); $client = $ucrmApi->get("clients/{$payment['clientId']}"); $payload = [ 'uuid' => 'manual-trigger', 'changeType' => 'insert', 'entity' => 'payment', 'entityId' => (int)$paymentId, 'eventName' => 'payment.add', 'clientData' => $client, 'paymentData' => $payment ]; // Internal Loopback $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; $selfUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']; $debugLogPath = __DIR__ . '/data/debug_public.log'; file_put_contents($debugLogPath, "[" . date('Y-m-d H:i:s') . "] RESEND PAYMENT. URL: $selfUrl" . PHP_EOL, FILE_APPEND); $ch = curl_init($selfUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_POSTREDIR, 3); // Mantener POST y payload en redirects 301/302 curl_setopt($ch, CURLOPT_TIMEOUT, 5); $res = curl_exec($ch); $err = curl_error($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); file_put_contents($debugLogPath, "[" . date('Y-m-d H:i:s') . "] RESEND CURL RESULT - Code: $code - Error: $err - Body: $res" . PHP_EOL, FILE_APPEND); echo json_encode(['success' => true, 'message' => 'Notificación disparada.']); } catch (\Exception $e) { echo json_encode(['success' => false, 'message' => $e->getMessage()]); } exit; } if ($_POST['action'] === 'resend_job_notification') { $jobId = $_POST['jobId'] ?? null; if (!$jobId) { echo json_encode(['success' => false, 'message' => 'jobId requerido']); exit; } try { $job = $ucrmApi->get("scheduling/jobs/$jobId"); $client = $ucrmApi->get("clients/{$job['clientId']}"); // Simular webhook job.edit con activación (status 0→1) $entityBeforeEdit = $job; $entityBeforeEdit['status'] = 0; // Simular que estaba en "Abierto" $entity = $job; $entity['status'] = 1; // Estado actual: "En curso" // Agregar prefijo para que verifyJobActionToDo reconozca que debe notificar $entity['title'] = '[NOTIFICACION-PENDIENTE]' . ($entity['title'] ?? ''); $payload = [ 'uuid' => 'manual-trigger', 'changeType' => 'update', 'entity' => 'job', 'entityId' => (int)$jobId, 'eventName' => 'job.edit', 'extraData' => [ 'entity' => $entity, 'entityBeforeEdit' => $entityBeforeEdit ] ]; $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; $selfUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']; $ch = curl_init($selfUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_POSTREDIR, 3); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $res = curl_exec($ch); $err = curl_error($ch); curl_close($ch); echo json_encode(['success' => true, 'message' => 'Notificación reenviada para job #' . $jobId]); } catch (\Exception $e) { echo json_encode(['success' => false, 'message' => $e->getMessage()]); } exit; } } if (isset($_GET['action'])) { header('Content-Type: application/json'); if ($_GET['action'] === 'search_clients') { $q = $_GET['q'] ?? ''; try { $clients = $ucrmApi->get('clients', ['query' => $q, 'limit' => 6]); echo json_encode($clients); } catch (\Exception $e) { echo json_encode([]); } exit; } if ($_GET['action'] === 'get_payments') { $clientId = $_GET['clientId'] ?? null; try { $payments = $ucrmApi->get('payments', ['clientId' => $clientId, 'limit' => 20, 'order' => 'createdDate', 'direction' => 'DESC']); $methods = $ucrmApi->get('payment-methods'); $methodMap = []; foreach ($methods as $m) $methodMap[$m['id']] = $m['name']; $formatted = array_slice($payments, 0, 10); foreach ($formatted as &$p) $p['methodName'] = $methodMap[$p['methodId']] ?? 'N/A'; echo json_encode($formatted); } catch (\Exception $e) { echo json_encode([]); } exit; } if ($_GET['action'] === 'search_stripe') { $q = $_GET['q'] ?? ''; echo json_encode($paymentIntentService->searchClients($q)); exit; } if ($_GET['action'] === 'get_stripe_details') { $id = $_GET['id'] ?? null; if ($id) { echo json_encode($paymentIntentService->getClientDetails($id)); } else { echo json_encode(['error' => 'Missing ID']); } exit; } if ($_GET['action'] === 'get_stripe_history') { $stripeCustomerId = $_GET['stripeCustomerId'] ?? null; if ($stripeCustomerId) { echo json_encode(['history' => $paymentIntentService->getLastPayments($stripeCustomerId, 10)]); } else { echo json_encode(['error' => 'Missing customer id']); } exit; } if ($_GET['action'] === 'get_oxxo_history') { $stripeCustomerId = $_GET['stripeCustomerId'] ?? null; if ($stripeCustomerId) { echo json_encode(['history' => $paymentIntentService->getLastOxxoPayments($stripeCustomerId, 5)]); } else { echo json_encode(['error' => 'Missing customer id']); } exit; } if ($_GET['action'] === 'get_installer_jobs') { $installerId = $_GET['installerId'] ?? null; if (!$installerId) { echo json_encode([]); exit; } try { $jobs = $ucrmApi->get('scheduling/jobs', [ 'assignedUserId' => $installerId, 'statuses[]' => 1, 'limit' => 50 ]); $result = []; foreach ($jobs as $job) { $clientName = 'Sin cliente'; if (!empty($job['clientId'])) { try { $client = $ucrmApi->get("clients/{$job['clientId']}"); $clientName = trim(($client['firstName'] ?? '') . ' ' . ($client['lastName'] ?? '')); } catch (\Exception $e) { $clientName = 'Cliente #' . $job['clientId']; } } $result[] = [ 'id' => $job['id'], 'title' => $job['title'] ?? 'Sin título', 'date' => $job['date'] ?? '', 'clientName' => $clientName, 'clientId' => $job['clientId'] ?? null, 'description' => mb_substr($job['description'] ?? '', 0, 80) ]; } echo json_encode($result); } catch (\Exception $e) { echo json_encode([]); } exit; } if ($_GET['action'] === 'image' || $_GET['action'] === 'get_image') { // Image Handler if (ob_get_level()) ob_end_clean(); $filename = basename($_GET['file'] ?? $_GET['name'] ?? ''); $paths = [__DIR__ . '/img/' . $filename, __DIR__ . '/img/webp/' . $filename, __DIR__ . '/vouchers_oxxo/' . $filename]; $finalPath = null; foreach ($paths as $p) { if (file_exists($p)) { $finalPath = $p; break; } } if ($finalPath) { $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); $type = ($ext === 'png') ? 'image/png' : (($ext === 'webp') ? 'image/webp' : 'image/jpeg'); header("Content-Type: $type"); header("Content-Length: " . filesize($finalPath)); readfile($finalPath); } else { http_response_code(404); echo "Not Found"; } exit; } } // POST Actions for Intents if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { header('Content-Type: application/json'); if ($_POST['action'] === 'create_oxxo_intent') { try { $builder = new \DI\ContainerBuilder(); $container = $builder->build(); $oxxoService = $container->get(\SmsNotifier\Facade\PluginOxxoNotifierFacade::class); if (ob_get_length()) ob_clean(); $result = $oxxoService->createOxxoPaymentIntent(['client_id' => $_POST['clientId']], $_POST['amount'], false); if (ob_get_length()) ob_clean(); echo json_encode(['success' => true, 'data' => $result]); } catch (\Exception $e) { echo json_encode(['success' => false, 'error' => $e->getMessage()]); } exit; } } $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instaladores":[]}', true); ?> SIIP - Notificaciones y Pagos

Portal Administrativo de Pagos de STRIPE y Notificaciones WhatsApp

Administración de Notificaciones vía WhatsApp, Intenciones de pago con Stripe y Fichas de OXXO Pay