loadConfig();
$logger = new \SmsNotifier\Service\Logger();
// 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'] ?? '');
$stripeService = new PaymentIntentService(
$ucrmApi,
$config['tokenstripe'] ?? '',
$logger
);
// 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) {
$logger->info("NMS Login OK for user: {$username}");
echo json_encode(['success' => true, 'token' => $authToken, 'user' => $body]);
} elseif ($statusCode === 201) {
$logger->info("NMS Login requires 2FA for user: {$username}");
http_response_code(201);
echo json_encode(['requires2FA' => true, 'twoFactorToken' => $body]);
} else {
$logger->warning("NMS Login failed for user: {$username} (HTTP {$statusCode})");
http_response_code($statusCode ?: 401);
echo json_encode(['error' => $body['message'] ?? 'Credenciales inválidas.', 'statusCode' => $statusCode]);
}
} catch (\Exception $e) {
$logger->error("NMS Login exception: " . $e->getMessage());
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) {
$logger->info("NMS 2FA Login OK");
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'];
$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_TIMEOUT, 5);
$res = curl_exec($ch);
curl_close($ch);
echo json_encode(['success' => true, 'message' => 'Notificación disparada.']);
} 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'] === 'get_stripe_history') {
if (ob_get_level()) ob_end_clean();
header('Content-Type: application/json');
$stripeCustomerId = $_GET['stripeCustomerId'] ?? '';
if (!$stripeCustomerId) {
echo json_encode(['error' => 'Missing stripeCustomerId']);
exit;
}
try {
$history = $stripeService->getLastPayments($stripeCustomerId);
$balance = $stripeService->getCustomerCashBalance($stripeCustomerId);
echo json_encode([
'history' => $history,
'cashBalance' => $balance
]);
} catch (Exception $e) {
echo json_encode(['error' => $e->getMessage()]);
}
exit;
}
if ($_GET['action'] === 'get_oxxo_history') {
if (ob_get_level()) ob_end_clean();
header('Content-Type: application/json');
$stripeCustomerId = $_GET['stripeCustomerId'] ?? '';
if (!$stripeCustomerId) {
echo json_encode(['error' => 'Missing stripeCustomerId']);
exit;
}
try {
$history = $stripeService->getLastOxxoPayments($stripeCustomerId);
echo json_encode(['history' => $history]);
} catch (Exception $e) {
echo json_encode(['error' => $e->getMessage()]);
}
exit;
}
if ($_GET['action'] === 'search_stripe') {
$q = $_GET['q'] ?? '';
echo json_encode($stripeService->searchClients($q));
exit;
}
if ($_GET['action'] === 'get_stripe_details') {
echo json_encode($stripeService->getClientDetails($_GET['id'] ?? null));
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_intent') {
$clientId = $_POST['clientId'] ?? null;
$amount = $_POST['amount'] ?? 0;
$stripeCustomerId = $_POST['stripeCustomerId'] ?? null;
$adminId = $_POST['adminId'] ?? null;
echo json_encode($stripeService->createPaymentIntent($clientId, $amount, $stripeCustomerId, $adminId));
exit;
}
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
SIIP Notifications
Inicia sesión con tu cuenta de UISP
Gestión de Instaladores
👷 Administra tu equipo de técnicos y asigna instalaciones de manera eficiente
| ID |
Nombre |
WhatsApp |
Acciones |
🔍
No se encontraron instaladores
Notificaciones WhatsApp
📱 Busca clientes y envía comprobantes de pago directamente a su WhatsApp
👋
Busca un cliente arriba para ver sus pagos y enviar notificaciones
| ID |
Fecha |
Monto |
Método |
Acción |
Pagos SPEI
🏦 Genera referencias de transferencia bancaria para que tus clientes paguen vía SPEI
🏦
Selecciona un cliente para generar una referencia de pago SPEI
Generar Intención de Pago
Historial de Pagos (Últimos 10)
Saldo Stripe: $0.00 MXN
| Estado |
Importe |
Descripción |
Fecha |
ID |
Pagos OXXO
🏪 Genera fichas de pago OXXO para que tus clientes paguen en tiendas de conveniencia
🏪
Selecciona un cliente para generar una ficha OXXO Pay
Vista Previa
La ficha generada aparecerá aquí
Historial de Fichas OXXO (Últimas 5)
| ID Pago |
Fecha |
Monto |
Ref. OXXO |
Estatus |
Ficha |
| Seleccione un cliente... |
Referencia Generada