db = \Config\Database::connect(); $this->baseUrl = rtrim((string) (getenv('EATRIA_BRIDGE_URL') ?: 'https://abdm-bridge.e-atria.in/api'), '/'); } public function consentRequest(array $payload): array { return $this->call('/v3/hiu/consent/request', $payload, 'consent.request'); } public function consentRequestStatus(array $payload): array { return $this->call('/v3/hiu/consent/request/status', $payload, 'consent.request.status'); } public function consentRequestFetch(array $payload): array { return $this->call('/v3/hiu/consent/request/fetch', $payload, 'consent.request.fetch'); } public function healthInformationRequest(array $payload): array { return $this->call('/v1/hiu/data/request', $payload, 'health-information.request'); } public function reconcileConsentStatus(array $payload): array { return $this->callGet('/v1/hiu/consent/status', $payload, 'consent.status.reconcile'); } public function fetchDecryptedData(array $payload): array { return $this->callGet('/v1/hiu/data/fetch', $payload, 'data.fetch'); } private function call(string $path, array $payload, string $eventKey): array { $settings = $this->resolveRuntimeSettings(); if (($settings['ok'] ?? 0) !== 1) { return $settings; } $token = (string) ($settings['token'] ?? ''); $hfrId = (string) ($settings['hfr_id'] ?? ''); $hospitalId = (string) ($settings['hospital_id'] ?? ''); $payloadHfrId = trim((string) ($payload['hfr_id'] ?? '')); if ($payloadHfrId !== '' && strcasecmp($payloadHfrId, $hfrId) !== 0) { return [ 'ok' => 0, 'http_code' => 400, 'error_text' => 'hfr_id mismatch: payload hfr_id does not match hospital_setting.ABDM_HFR_ID', 'retryable' => 0, ]; } $payload['hfr_id'] = $hfrId; $url = $this->baseUrl . $path; $headers = [ 'Content-Type: application/json', 'Accept: application/json', 'Authorization: Bearer ' . $token, ]; if ($hospitalId !== '') { $headers[] = 'X-Hospital-Id: ' . $hospitalId; } $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $this->timeoutSec, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ]); $raw = curl_exec($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlErr = curl_error($ch); curl_close($ch); $decoded = json_decode((string) $raw, true); $isJson = is_array($decoded); $ok = $isJson ? (int) ($decoded['ok'] ?? ($httpCode >= 200 && $httpCode < 300 ? 1 : 0)) : (int) ($httpCode >= 200 && $httpCode < 300); $errorText = ''; if ($curlErr !== '') { $errorText = $curlErr; } elseif ($ok !== 1) { $errorText = $isJson ? (string) ($decoded['message'] ?? $decoded['error_text'] ?? $decoded['error'] ?? ('HTTP ' . $httpCode)) : ('HTTP ' . $httpCode . ' non-JSON response'); } if ($this->isCloudFrontBlockedResponse($httpCode, $raw, $response ?? [])) { $errorText = 'Gateway edge block (CloudFront 403 Request blocked). Retry later or ask gateway team to unblock the endpoint.'; } $retryable = 0; if ($curlErr !== '' || $httpCode >= 500 || $httpCode === 0 || str_contains(strtolower($errorText), 'timed out')) { $retryable = 1; } $response = $isJson ? $decoded : ['raw' => (string) $raw]; $response['ok'] = $ok; $response['http_code'] = $httpCode; $response['error_text'] = $errorText; $response['retryable'] = $retryable; if ($this->isCloudFrontBlockedResponse($httpCode, $raw, $response)) { $response['retryable'] = 1; $response['error_text'] = 'Gateway edge block (CloudFront 403 Request blocked). Retry later or ask gateway team to unblock the endpoint.'; } $gatewayRequestId = trim((string) ($response['request_id'] ?? $response['requestId'] ?? '')); if ($gatewayRequestId === '') { $gatewayRequestId = trim((string) ($payload['requestId'] ?? $payload['request_id'] ?? '')); } $response['gateway_request_id'] = $gatewayRequestId; $abdmConsentRequestId = $this->findFirstValueByKeys($response, [ 'consentRequestId', 'consent_request_id', 'abdm_consent_request_id', ]); if ($abdmConsentRequestId !== '' && $this->isGatewayRefId($abdmConsentRequestId)) { $abdmConsentRequestId = ''; } if ($abdmConsentRequestId !== '') { $response['abdm_consent_request_id'] = $abdmConsentRequestId; $response['consent_request_id'] = $abdmConsentRequestId; } $abdmConsentId = $this->findFirstValueByKeys($response, [ 'consentId', 'consent_id', ]); if ($abdmConsentId !== '' && $this->isGatewayRefId($abdmConsentId)) { $abdmConsentId = ''; } if ($abdmConsentId !== '') { $response['consent_id'] = $abdmConsentId; $response['abdm_consent_artifact_id'] = $abdmConsentId; } if (! isset($response['request_id'])) { $response['request_id'] = $gatewayRequestId; } if (! isset($response['transaction_id'])) { $response['transaction_id'] = (string) ($response['transactionId'] ?? $payload['transactionId'] ?? $payload['transaction_id'] ?? ''); } $this->writeApiLog($eventKey, $path, $payload, $response, $httpCode, $ok, $errorText, $hospitalId); return $response; } private function callGet(string $path, array $query, string $eventKey): array { $settings = $this->resolveRuntimeSettings(); if (($settings['ok'] ?? 0) !== 1) { return $settings; } $token = (string) ($settings['token'] ?? ''); $hfrId = (string) ($settings['hfr_id'] ?? ''); $hospitalId = (string) ($settings['hospital_id'] ?? ''); $payloadHfrId = trim((string) ($query['hfr_id'] ?? '')); if ($payloadHfrId !== '' && strcasecmp($payloadHfrId, $hfrId) !== 0) { return [ 'ok' => 0, 'http_code' => 400, 'error_text' => 'hfr_id mismatch: payload hfr_id does not match hospital_setting.ABDM_HFR_ID', 'retryable' => 0, ]; } $headers = [ 'Accept: application/json', 'Authorization: Bearer ' . $token, ]; if ($hospitalId !== '') { $headers[] = 'X-Hospital-Id: ' . $hospitalId; } $cleanQuery = []; foreach ($query as $k => $v) { if ($v === null) { continue; } $val = trim((string) $v); if ($val === '') { continue; } $cleanQuery[$k] = $val; } // Bridge polling APIs require hfr_id in query for auth scope validation. if ($hfrId !== '') { $cleanQuery['hfr_id'] = $hfrId; } $url = $this->baseUrl . $path; if (! empty($cleanQuery)) { $url .= '?' . http_build_query($cleanQuery); } $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $this->timeoutSec, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_CUSTOMREQUEST => 'GET', ]); $raw = curl_exec($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlErr = curl_error($ch); curl_close($ch); $decoded = json_decode((string) $raw, true); $isJson = is_array($decoded); $ok = $isJson ? (int) ($decoded['ok'] ?? ($httpCode >= 200 && $httpCode < 300 ? 1 : 0)) : (int) ($httpCode >= 200 && $httpCode < 300); $errorText = ''; if ($curlErr !== '') { $errorText = $curlErr; } elseif ($ok !== 1) { $errorText = $isJson ? (string) ($decoded['message'] ?? $decoded['error_text'] ?? $decoded['error'] ?? ('HTTP ' . $httpCode)) : ('HTTP ' . $httpCode . ' non-JSON response'); } if ($this->isCloudFrontBlockedResponse($httpCode, $raw, $decoded ?? [])) { $errorText = 'Gateway edge block (CloudFront 403 Request blocked). Retry later or ask gateway team to unblock the endpoint.'; } $retryable = 0; if ($curlErr !== '' || $httpCode >= 500 || $httpCode === 0 || str_contains(strtolower($errorText), 'timed out')) { $retryable = 1; } $response = $isJson ? $decoded : ['raw' => (string) $raw]; $response['ok'] = $ok; $response['http_code'] = $httpCode; $response['error_text'] = $errorText; $response['retryable'] = $retryable; if ($this->isCloudFrontBlockedResponse($httpCode, $raw, $response)) { $response['retryable'] = 1; $response['error_text'] = 'Gateway edge block (CloudFront 403 Request blocked). Retry later or ask gateway team to unblock the endpoint.'; } $gatewayRequestId = trim((string) ($response['request_id'] ?? $response['requestId'] ?? $cleanQuery['request_id'] ?? '')); $response['gateway_request_id'] = $gatewayRequestId; $abdmConsentRequestId = $this->findFirstValueByKeys($response, [ 'consentRequestId', 'consent_request_id', 'abdm_consent_request_id', ]); if ($abdmConsentRequestId !== '' && ! $this->isGatewayRefId($abdmConsentRequestId)) { $response['abdm_consent_request_id'] = $abdmConsentRequestId; $response['consent_request_id'] = $abdmConsentRequestId; } $abdmConsentId = $this->findFirstValueByKeys($response, [ 'consentId', 'consent_id', 'abdm_consent_artifact_id', ]); if ($abdmConsentId !== '' && ! $this->isGatewayRefId($abdmConsentId)) { $response['consent_id'] = $abdmConsentId; $response['abdm_consent_artifact_id'] = $abdmConsentId; } if (! isset($response['transaction_id'])) { $response['transaction_id'] = (string) ($response['transactionId'] ?? $cleanQuery['transaction_id'] ?? ''); } $this->writeApiLog($eventKey, $path, $cleanQuery, $response, $httpCode, $ok, $errorText, $hospitalId, 'GET'); return $response; } private function resolveRuntimeSettings(): array { if (! $this->db->tableExists('hospital_setting')) { return ['ok' => 0, 'http_code' => 500, 'error_text' => 'hospital_setting table not found', 'retryable' => 0]; } $rows = $this->db->table('hospital_setting') ->select('s_name, s_value') ->whereIn('s_name', ['EATRIA_BRIDGE_TOKEN', 'ABDM_HFR_ID', 'ABDM_HOSPITAL_ID']) ->get() ->getResultArray(); $kv = []; foreach ($rows as $row) { $key = trim((string) ($row['s_name'] ?? '')); if ($key === '') { continue; } $kv[$key] = trim((string) ($row['s_value'] ?? '')); } $token = (string) ($kv['EATRIA_BRIDGE_TOKEN'] ?? ''); $hfrId = (string) ($kv['ABDM_HFR_ID'] ?? ''); $hospitalId = (string) ($kv['ABDM_HOSPITAL_ID'] ?? ''); if ($token === '') { return [ 'ok' => 0, 'http_code' => 401, 'error_text' => 'Missing token: set hospital_setting.EATRIA_BRIDGE_TOKEN', 'retryable' => 0, ]; } if ($hfrId === '') { return [ 'ok' => 0, 'http_code' => 400, 'error_text' => 'Missing hfr_id: set hospital_setting.ABDM_HFR_ID', 'retryable' => 0, ]; } return [ 'ok' => 1, 'token' => $token, 'hfr_id' => $hfrId, 'hospital_id' => $hospitalId, ]; } private function writeApiLog( string $eventKey, string $path, array $request, array $response, int $httpCode, int $ok, string $errorText, string $hospitalId, string $httpMethod = 'POST' ): void { if (! $this->db->tableExists('abdm_api_logs')) { return; } $status = $ok === 1 ? 'success' : 'error'; $entityId = trim((string) ( $request['consent_id'] ?? $request['consentId'] ?? $request['consentRequestId'] ?? $request['request_id'] ?? $request['requestId'] ?? $request['transaction_id'] ?? $request['transactionId'] ?? '' )); $this->db->table('abdm_api_logs')->insert([ 'channel' => 'eatria_bridge', 'event_type' => 'abdm.m3.hiu.' . $eventKey, 'endpoint' => $this->baseUrl . $path, 'http_method' => strtoupper(trim($httpMethod)) !== '' ? strtoupper(trim($httpMethod)) : 'POST', 'entity_type' => 'abdm_hiu', 'entity_id' => $entityId, 'request_json' => (string) json_encode($request, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 'response_code' => $httpCode, 'response_json' => (string) json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 'status' => $status, 'error_message' => $errorText !== '' ? mb_substr($errorText, 0, 1000) : null, 'created_at' => date('Y-m-d H:i:s'), ]); } private function findFirstValueByKeys(array $root, array $keys): string { $queue = [$root]; while (! empty($queue)) { $node = array_shift($queue); if (! is_array($node)) { continue; } foreach ($keys as $key) { if (isset($node[$key])) { $value = trim((string) $node[$key]); if ($value !== '') { return $value; } } } foreach ($node as $child) { if (is_array($child)) { $queue[] = $child; } } } return ''; } private function isGatewayRefId(string $value): bool { return preg_match('/^REQ-/i', trim($value)) === 1; } private function isCloudFrontBlockedResponse(int $httpCode, $raw, array $response): bool { if ($httpCode !== 403) { return false; } $text = strtolower((string) $raw); if ($text === '' && isset($response['data']['raw'])) { $text = strtolower((string) $response['data']['raw']); } return str_contains($text, 'generated by cloudfront') || str_contains($text, 'request blocked') || str_contains($text, 'the request could not be satisfied'); } }