canManageSettings()) { return $this->response->setStatusCode(403)->setBody('Access denied'); } $token = $this->readSettingValue('EATRIA_BRIDGE_TOKEN'); $hfrId = $this->readSettingValue('ABDM_HFR_ID'); $hmsName = $this->readSettingValue('ABDM_HMS_NAME'); $gwUrl = $this->readSettingValue('EATRIA_BRIDGE_URL') ?: 'https://abdm-bridge.e-atria.in/api'; $bridgeHospitalId = $this->readSettingValue('ABDM_BRIDGE_HOSPITAL_ID'); return view('Setting/Admin/abdm_gateway_settings', [ 'gateway_url' => $gwUrl, 'hfr_id' => $hfrId, 'hms_name' => $hmsName, 'bridge_hospital_id' => $bridgeHospitalId, 'token_masked' => $this->maskKey($token), 'token_exists' => $token !== '', 'connector' => $this->readSettingValue('ABDM_CONNECTOR') ?: 'eatria_bridge', 'abdm_sync_provider' => $this->readSettingValue('ABDM_SYNC_PROVIDER') ?: 'eatria', ]); } public function save() { if (! $this->request->isAJAX()) { return $this->response->setJSON(['update' => 0, 'error_text' => 'Invalid request']); } if (! $this->canManageSettings()) { return $this->response->setStatusCode(403)->setJSON(['update' => 0, 'error_text' => 'Access denied']); } if (! $this->db->tableExists('hospital_setting')) { return $this->response->setJSON(['update' => 0, 'error_text' => 'hospital_setting table not found']); } $gwUrl = trim((string) $this->request->getPost('gateway_url')); $token = trim((string) $this->request->getPost('api_token')); $hfrId = trim((string) $this->request->getPost('hfr_id')); $hmsName = trim((string) $this->request->getPost('hms_name')); // Validate gateway URL format if ($gwUrl !== '' && ! filter_var($gwUrl, FILTER_VALIDATE_URL)) { return $this->response->setJSON([ 'update' => 0, 'error_text' => 'Gateway URL is not a valid URL.', 'csrfName' => csrf_token(), 'csrfHash' => csrf_hash(), ]); } $saved = 0; if ($gwUrl !== '') { $this->upsertSettingValue('EATRIA_BRIDGE_URL', rtrim($gwUrl, '/')); $saved++; } if ($token !== '') { $this->upsertSettingValue('EATRIA_BRIDGE_TOKEN', $token); $saved++; } if ($hfrId !== '') { $this->upsertSettingValue('ABDM_HFR_ID', $hfrId); $saved++; } if ($hmsName !== '') { $this->upsertSettingValue('ABDM_HMS_NAME', $hmsName); $saved++; } // Always set connector to eatria_bridge when saving from this page $this->upsertSettingValue('ABDM_CONNECTOR', 'eatria_bridge'); $this->upsertSettingValue('ABDM_SYNC_PROVIDER', 'eatria'); if ($saved === 0) { return $this->response->setJSON([ 'update' => 0, 'error_text' => 'Provide at least one value to save.', 'csrfName' => csrf_token(), 'csrfHash' => csrf_hash(), ]); } $storedToken = $this->readSettingValue('EATRIA_BRIDGE_TOKEN'); return $this->response->setJSON([ 'update' => 1, 'error_text' => 'ABDM Gateway settings saved.', 'token_exists' => $storedToken !== '', 'token_masked' => $this->maskKey($storedToken), 'csrfName' => csrf_token(), 'csrfHash' => csrf_hash(), ]); } public function testConnection() { if (! $this->request->isAJAX()) { return $this->response->setJSON(['update' => 0, 'error_text' => 'Invalid request']); } if (! $this->canManageSettings()) { return $this->response->setStatusCode(403)->setJSON(['update' => 0, 'error_text' => 'Access denied']); } // Use posted values first, then stored settings $gwUrl = trim((string) $this->request->getPost('gateway_url')); $token = trim((string) $this->request->getPost('api_token')); $hfrId = trim((string) $this->request->getPost('hfr_id')); if ($gwUrl === '') { $gwUrl = $this->readSettingValue('EATRIA_BRIDGE_URL') ?: 'https://abdm-bridge.e-atria.in/api'; } if ($token === '') { $token = $this->readSettingValue('EATRIA_BRIDGE_TOKEN'); } if ($hfrId === '') { $hfrId = $this->readSettingValue('ABDM_HFR_ID'); } $gwUrl = rtrim($gwUrl, '/'); // ── Step 1: Health check — include Bearer if available (gateway may require auth even on /health) ── $healthUrl = $gwUrl . '/v3/health' . ($hfrId !== '' ? '?hfr_id=' . urlencode($hfrId) : ''); $healthHeaders = ['Accept: application/json']; if ($token !== '') { $healthHeaders[] = 'Authorization: Bearer ' . $token; } $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $healthUrl, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_HTTPHEADER => $healthHeaders, ]); $rawHealth = (string) curl_exec($ch); $codeHealth = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlErrHealth = curl_error($ch); curl_close($ch); if ($curlErrHealth !== '') { $this->writeTestLog($healthUrl, 'GET', [], 0, '', 'error', 'cURL error: ' . $curlErrHealth); return $this->response->setJSON([ 'update' => 0, 'error_text' => 'Cannot reach gateway: ' . $curlErrHealth, 'csrfName' => csrf_token(), 'csrfHash' => csrf_hash(), ]); } $healthBody = json_decode($rawHealth, true); $healthOk = $codeHealth === 200 && is_array($healthBody) && ($healthBody['status'] ?? '') === 'ok'; // If health returned 401/403 with no token, report "no token" not "unreachable" if (! $healthOk && in_array($codeHealth, [401, 403], true) && $token === '') { $this->writeTestLog($healthUrl, 'GET', [], $codeHealth, $rawHealth, 'error', 'Health check HTTP ' . $codeHealth . ' — no token configured'); return $this->response->setJSON([ 'update' => 0, 'error_text' => 'Gateway reachable but API token required. Save your token first then re-test.', 'csrfName' => csrf_token(), 'csrfHash' => csrf_hash(), ]); } $hfrIdOk = (bool) ($healthBody['hfr_id_ok'] ?? false); $hfrIdMsg = (string) ($healthBody['hfr_id_msg'] ?? ''); $mode = (string) ($healthBody['mode'] ?? 'unknown'); $version = (string) ($healthBody['version'] ?? ''); $this->writeTestLog( $healthUrl, 'GET', [], $codeHealth, $rawHealth, $healthOk ? 'success' : 'error', $healthOk ? '' : ('Health check HTTP ' . $codeHealth) ); if (! $healthOk) { return $this->response->setJSON([ 'update' => 0, 'error_text' => 'Gateway unreachable or health check failed (HTTP ' . $codeHealth . ')', 'csrfName' => csrf_token(), 'csrfHash' => csrf_hash(), ]); } // ── Step 2: Bearer auth check via /v3/gateway/status ─────────────── if ($token === '') { return $this->response->setJSON([ 'update' => 0, 'error_text' => 'Gateway is reachable' . ($hfrIdMsg !== '' ? ' — ' . $hfrIdMsg : '') . '. But no API token configured — save your token first.', 'mode' => $mode, 'version' => $version, 'hfr_id_ok' => $hfrIdOk, 'hfr_id_msg' => $hfrIdMsg, 'csrfName' => csrf_token(), 'csrfHash' => csrf_hash(), ]); } $statusUrl = $gwUrl . '/v3/gateway/status'; $ch2 = curl_init(); curl_setopt_array($ch2, [ CURLOPT_URL => $statusUrl, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_HTTPHEADER => [ 'Accept: application/json', 'Authorization: Bearer ' . $token, ], ]); $rawStatus = (string) curl_exec($ch2); $codeStatus = (int) curl_getinfo($ch2, CURLINFO_HTTP_CODE); $curlErrStatus = curl_error($ch2); curl_close($ch2); if ($curlErrStatus !== '') { $this->writeTestLog($statusUrl, 'GET', [], 0, '', 'error', 'cURL error: ' . $curlErrStatus); return $this->response->setJSON([ 'update' => 0, 'error_text' => 'Auth check failed: ' . $curlErrStatus, 'csrfName' => csrf_token(), 'csrfHash' => csrf_hash(), ]); } $statusBody = json_decode($rawStatus, true); // Token is valid when authenticated_as is present (ok:0 means ABDM upstream unreachable, not bad token) // Per API docs: ok:1 = full success, ok:0+authenticated_as = token ok but ABDM upstream down $abdmUpstreamOk = $codeStatus === 200 && is_array($statusBody) && ( (int) ($statusBody['ok'] ?? 0) === 1 || ($statusBody['status'] ?? '') === 'ok' ); $authOk = $abdmUpstreamOk || ( $codeStatus === 200 && is_array($statusBody) && !empty($statusBody['authenticated_as']) ); $authInfo = null; if ($authOk && isset($statusBody['authenticated_as']) && is_array($statusBody['authenticated_as'])) { $a = $statusBody['authenticated_as']; $authInfo = [ 'principal' => (string) ($a['principal'] ?? ''), 'hospital_id' => (string) ($a['hospital_id'] ?? ''), 'hfr_id' => (string) ($a['hfr_id'] ?? ''), 'type' => (string) ($a['type'] ?? ''), ]; // Auto-persist hfr_id and principal name if not already stored if ($this->db->tableExists('hospital_setting')) { if ($authInfo['hfr_id'] !== '' && $this->readSettingValue('ABDM_HFR_ID') === '') { $this->upsertSettingValue('ABDM_HFR_ID', $authInfo['hfr_id']); } if ($authInfo['hospital_id'] !== '') { $this->upsertSettingValue('ABDM_BRIDGE_HOSPITAL_ID', $authInfo['hospital_id']); } if ($authInfo['principal'] !== '' && $this->readSettingValue('ABDM_HMS_NAME') === '') { $this->upsertSettingValue('ABDM_HMS_NAME', $authInfo['principal']); } } } $authErrMsg = $authOk ? '' : ('HTTP ' . $codeStatus . ($codeStatus === 401 ? ' — invalid API key' : ($codeStatus === 403 ? ' — access denied' : ($codeStatus === 200 ? ' — unexpected response format: ' . mb_substr($rawStatus, 0, 120) : '')))); $this->writeTestLog($statusUrl, 'GET', [], $codeStatus, $rawStatus, $authOk ? 'success' : 'error', $authErrMsg); if (! $authOk) { return $this->response->setJSON([ 'update' => 0, 'error_text' => 'Gateway reachable' . ($hfrIdMsg !== '' ? ' (' . $hfrIdMsg . ')' : '') . ' but auth failed: ' . $authErrMsg, 'mode' => $mode, 'version' => $version, 'hfr_id_ok' => $hfrIdOk, 'hfr_id_msg' => $hfrIdMsg, 'auth_ok' => false, 'csrfName' => csrf_token(), 'csrfHash' => csrf_hash(), ]); } $abdmUpstreamNote = $abdmUpstreamOk ? '' : ' | ⚠ ABDM upstream: unreachable (gateway ok:0)'; return $this->response->setJSON([ 'update' => 1, 'error_text' => 'Gateway OK — ' . ($hfrIdMsg ?: 'connected') . ' | Token authenticated ✓' . $abdmUpstreamNote . ($mode !== 'unknown' ? ' | mode: ' . $mode : '') . ($version !== '' ? ', v' . $version : ''), 'mode' => $mode, 'version' => $version, 'hfr_id_ok' => $hfrIdOk, 'hfr_id_msg' => $hfrIdMsg, 'auth_ok' => true, 'abdm_upstream_ok' => $abdmUpstreamOk, 'auth_msg' => 'API key authenticated ✓', 'auth_info' => $authInfo, 'csrfName' => csrf_token(), 'csrfHash' => csrf_hash(), ]); } /** * Write a test-connection entry to abdm_api_logs (fail-open). */ private function writeTestLog( string $endpoint, string $method, array $requestBody, int $httpCode, string $rawResponse, string $status, string $errorMessage ): void { try { if (! $this->db->tableExists('abdm_api_logs')) { return; } $decoded = json_decode($rawResponse, true); $responseJson = is_array($decoded) ? json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : (trim($rawResponse) !== '' ? $rawResponse : null); $this->db->table('abdm_api_logs')->insert([ 'channel' => 'eatria_bridge', 'event_type' => 'gateway.test_connection', 'endpoint' => $endpoint, 'http_method' => strtoupper($method), 'entity_type' => null, 'entity_id' => null, 'request_json' => $requestBody !== [] ? json_encode($requestBody, JSON_UNESCAPED_UNICODE) : null, 'response_code' => $httpCode > 0 ? $httpCode : null, 'response_json' => $responseJson, 'status' => $status, 'error_message' => $errorMessage !== '' ? mb_substr($errorMessage, 0, 1000) : null, 'created_at' => date('Y-m-d H:i:s'), ]); } catch (\Throwable $e) { log_message('warning', '[AbdmGatewaySettings] writeTestLog failed: ' . $e->getMessage()); } } // ------------------------------------------------------------------------- // Helpers (same pattern as HealthplixSettings) // ------------------------------------------------------------------------- private function canManageSettings(): bool { if (! function_exists('auth') || ! auth()->loggedIn()) { return false; } return true; } private function readSettingValue(string $name): string { if (defined($name)) { $v = trim((string) constant($name)); if ($v !== '') { return $v; } } if (! $this->db->tableExists('hospital_setting')) { return ''; } $row = $this->db->table('hospital_setting') ->select('s_value') ->where('s_name', $name) ->get(1) ->getRowArray(); return trim((string) ($row['s_value'] ?? '')); } private function upsertSettingValue(string $name, string $value): bool { $existing = $this->db->table('hospital_setting') ->select('id, s_value') ->where('s_name', $name) ->get(1) ->getRowArray(); if ($existing) { return (bool) $this->db->table('hospital_setting') ->where('id', (int) $existing['id']) ->update(['s_value' => $value]); } return (bool) $this->db->table('hospital_setting')->insert([ 's_name' => $name, 's_value' => $value, ]); } private function maskKey(string $key): string { $key = trim($key); if ($key === '') { return ''; } if (mb_strlen($key) <= 8) { return str_repeat('*', mb_strlen($key)); } return mb_substr($key, 0, 4) . str_repeat('*', mb_strlen($key) - 8) . mb_substr($key, -4); } }