ABDM HIU callback hardening, OPD ABHA optional confirm, and AJAX memory cleanup
This commit is contained in:
parent
658e67ad3b
commit
acabc6abdd
|
|
@ -624,6 +624,10 @@ $routes->post('AbdmGateway/scan_share_link_patient/(:num)', 'AbdmGateway::scanSh
|
|||
$routes->post('AbdmGateway/scan_share_lookup_result_callback', 'AbdmGateway::scanShareLookupResultCallback');
|
||||
$routes->post('AbdmGateway/consent_request', 'AbdmGateway::consentRequest', ['filter' => $abdmPermFilter]);
|
||||
$routes->post('AbdmGateway/consent_callback', 'AbdmGateway::consentCallback');
|
||||
$routes->post('AbdmGateway/hip_consent_notify_callback', 'AbdmGateway::hipConsentNotifyCallback');
|
||||
$routes->post('api/v3/hiu/consent/on-notify', 'AbdmGateway::hipConsentNotifyCallback');
|
||||
$routes->post('api/v3/hiu/consent/on_notify', 'AbdmGateway::hipConsentNotifyCallback');
|
||||
$routes->post('api/v3/hiu/consent/notify', 'AbdmGateway::hipConsentNotifyCallback');
|
||||
$routes->post('AbdmGateway/consent_revoked_callback', 'AbdmGateway::consentRevokedCallback');
|
||||
$routes->post('AbdmGateway/record_linked_callback', 'AbdmGateway::recordLinkedCallback');
|
||||
// ABDM M3 HIU (HMS -> Gateway)
|
||||
|
|
@ -637,9 +641,17 @@ $routes->post('AbdmHiu/consent_request_fetch', 'AbdmHiu::consentRequestFetch', [
|
|||
$routes->post('AbdmHiu/health_information_request', 'AbdmHiu::healthInformationRequest', ['filter' => $abdmPermFilter]);
|
||||
$routes->post('AbdmHiu/consent_reconcile', 'AbdmHiu::consentReconcile', ['filter' => $abdmPermFilter]);
|
||||
$routes->post('AbdmHiu/data_fetch', 'AbdmHiu::dataFetch', ['filter' => $abdmPermFilter]);
|
||||
$routes->post('AbdmHiu/consent_update_callback', 'AbdmHiu::consentUpdateWebhook');
|
||||
$routes->post('AbdmHiu/health_information_on_request_callback', 'AbdmHiu::healthInformationOnRequestWebhook');
|
||||
$routes->post('AbdmHiu/health_information_data_push_callback', 'AbdmHiu::healthInformationDataPushWebhook');
|
||||
$routes->post('api/v3/hiu/consent/request', 'AbdmHiu::consentRequest', ['filter' => $abdmPermFilter]);
|
||||
$routes->post('api/v3/hiu/consent/request/status', 'AbdmHiu::consentRequestStatus', ['filter' => $abdmPermFilter]);
|
||||
$routes->post('api/v3/hiu/consent/request/fetch', 'AbdmHiu::consentRequestFetch', ['filter' => $abdmPermFilter]);
|
||||
$routes->post('api/v3/hiu/consent/on-update', 'AbdmHiu::consentUpdateWebhook');
|
||||
$routes->post('api/v3/hiu/consent/on_update', 'AbdmHiu::consentUpdateWebhook');
|
||||
$routes->post('api/v3/hiu/health-information/on-request', 'AbdmHiu::healthInformationOnRequestWebhook');
|
||||
$routes->post('api/v3/hiu/health-information/on_request', 'AbdmHiu::healthInformationOnRequestWebhook');
|
||||
$routes->post('api/v3/hiu/data/push', 'AbdmHiu::healthInformationDataPushWebhook');
|
||||
$routes->post('api/v3/hiu/health-information/request', 'AbdmHiu::healthInformationRequest', ['filter' => $abdmPermFilter]);
|
||||
$routes->post('api/v1/hiu/consent/status', 'AbdmHiu::consentReconcile', ['filter' => $abdmPermFilter]);
|
||||
$routes->post('api/v1/hiu/data/fetch', 'AbdmHiu::dataFetch', ['filter' => $abdmPermFilter]);
|
||||
|
|
|
|||
|
|
@ -670,6 +670,121 @@ class AbdmGateway extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// HIP Consent Notify Callback — gateway forwards consent notify updates.
|
||||
// POST /AbdmGateway/hip_consent_notify_callback (no auth filter — public webhook)
|
||||
// =========================================================================
|
||||
|
||||
public function hipConsentNotifyCallback()
|
||||
{
|
||||
$signatureFailure = $this->validateWebhookSignature();
|
||||
if ($signatureFailure !== null) {
|
||||
return $signatureFailure;
|
||||
}
|
||||
|
||||
$payload = $this->request->getJSON(true);
|
||||
if (! is_array($payload) || empty($payload)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => 'Invalid JSON payload',
|
||||
]);
|
||||
}
|
||||
|
||||
$consentObj = [];
|
||||
if (is_array($payload['consent'] ?? null)) {
|
||||
$consentObj = (array) $payload['consent'];
|
||||
} elseif (is_array($payload['consentDetail'] ?? null)) {
|
||||
$consentObj = (array) $payload['consentDetail'];
|
||||
}
|
||||
|
||||
$requestId = trim((string) (
|
||||
$payload['request_id']
|
||||
?? $payload['requestId']
|
||||
?? ($payload['response']['requestId'] ?? '')
|
||||
?? ''
|
||||
));
|
||||
$consentId = trim((string) (
|
||||
$consentObj['id']
|
||||
?? $payload['consent_id']
|
||||
?? $payload['consentId']
|
||||
?? ''
|
||||
));
|
||||
$abhaAddress = trim((string) (
|
||||
$consentObj['patient']['id']
|
||||
?? $payload['abha_address']
|
||||
?? $payload['abha_id']
|
||||
?? ''
|
||||
));
|
||||
$statusRaw = trim((string) (
|
||||
$consentObj['status']
|
||||
?? $payload['consent_status']
|
||||
?? $payload['status']
|
||||
?? 'status_checked'
|
||||
));
|
||||
$status = strtolower($statusRaw);
|
||||
|
||||
// Keep existing M3 workflow in sync for dateRange and consent status lookups.
|
||||
try {
|
||||
$m3Service = new \App\Libraries\Abdm\M3HiuWorkflowService();
|
||||
$m3Service->ingestConsentUpdateWebhook($payload);
|
||||
} catch (\Throwable $e) {
|
||||
// Fail-open: callback must still ACK to avoid upstream retry storms.
|
||||
}
|
||||
|
||||
if ($this->db->tableExists('consent_logs')) {
|
||||
$logStatus = match ($status) {
|
||||
'granted', 'approved', 'active' => 'GRANTED',
|
||||
'revoked', 'denied' => 'REVOKED',
|
||||
'expired' => 'EXPIRED',
|
||||
default => 'GRANTED',
|
||||
};
|
||||
|
||||
$dateRange = $consentObj['permission']['dateRange'] ?? [];
|
||||
$expiresAt = trim((string) ($dateRange['to'] ?? $payload['expires_at'] ?? ''));
|
||||
|
||||
$this->db->table('consent_logs')->insert([
|
||||
'patient_id' => (int) ($payload['patient_id'] ?? 0),
|
||||
'abha_id' => $abhaAddress !== '' ? $abhaAddress : null,
|
||||
'consent_id' => $consentId !== '' ? $consentId : ($requestId !== '' ? $requestId : null),
|
||||
'purpose' => trim((string) ($consentObj['purpose']['text'] ?? '')) ?: null,
|
||||
'status' => $logStatus,
|
||||
'created_at' => Time::now('Asia/Kolkata')->toDateTimeString(),
|
||||
'expires_at' => $expiresAt !== '' ? $expiresAt : null,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->db->tableExists('abdm_api_logs')) {
|
||||
$this->db->table('abdm_api_logs')->insert([
|
||||
'channel' => 'bridge',
|
||||
'event_type' => 'abdm.hip.consent.notify.callback',
|
||||
'endpoint' => '/AbdmGateway/hip_consent_notify_callback',
|
||||
'http_method' => 'POST',
|
||||
'entity_type' => 'consent',
|
||||
'entity_id' => $consentId !== '' ? $consentId : $requestId,
|
||||
'request_json' => (string) json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||
'response_code' => 202,
|
||||
'response_json' => (string) json_encode([
|
||||
'ok' => 1,
|
||||
'status' => 'accepted',
|
||||
'request_id' => $requestId,
|
||||
'consent_id' => $consentId,
|
||||
'consent_status' => $statusRaw,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||
'status' => 'success',
|
||||
'error_message' => null,
|
||||
'created_at' => Time::now('Asia/Kolkata')->toDateTimeString(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setStatusCode(202)->setJSON([
|
||||
'ok' => 1,
|
||||
'status' => 'accepted',
|
||||
'request_id' => $requestId,
|
||||
'consent_id' => $consentId !== '' ? $consentId : null,
|
||||
'consent_status' => $statusRaw,
|
||||
]);
|
||||
}
|
||||
|
||||
public function scanShareLookup()
|
||||
{
|
||||
if (! $this->request->isAJAX()) {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,75 @@ class AbdmHiu extends BaseController
|
|||
return $this->handleOperation('data_fetch', 'DATA_PENDING');
|
||||
}
|
||||
|
||||
public function consentUpdateWebhook()
|
||||
{
|
||||
$signatureFailure = $this->validateGatewayWebhookSignature();
|
||||
if ($signatureFailure !== null) {
|
||||
return $signatureFailure;
|
||||
}
|
||||
|
||||
$payload = $this->request->getJSON(true);
|
||||
if (! is_array($payload) || empty($payload)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'ok' => 0,
|
||||
'error' => 'Invalid consent callback payload',
|
||||
]);
|
||||
}
|
||||
|
||||
$result = $this->service->ingestConsentUpdateWebhook($payload);
|
||||
$httpCode = (int) ($result['ok'] ?? 0) === 1 ? 202 : 400;
|
||||
|
||||
return $this->response->setStatusCode($httpCode)->setJSON($result);
|
||||
}
|
||||
|
||||
public function healthInformationOnRequestWebhook()
|
||||
{
|
||||
$signatureFailure = $this->validateGatewayWebhookSignature();
|
||||
if ($signatureFailure !== null) {
|
||||
return $signatureFailure;
|
||||
}
|
||||
|
||||
$payload = $this->request->getJSON(true);
|
||||
if (! is_array($payload) || empty($payload)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'ok' => 0,
|
||||
'error' => 'Invalid health-information callback payload',
|
||||
]);
|
||||
}
|
||||
|
||||
$result = $this->service->ingestHealthInformationCallback($payload, 'hi_on_request_callback');
|
||||
|
||||
return $this->response->setStatusCode(202)->setJSON([
|
||||
'ok' => 1,
|
||||
'status' => 'accepted',
|
||||
'result' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
public function healthInformationDataPushWebhook()
|
||||
{
|
||||
$signatureFailure = $this->validateGatewayWebhookSignature();
|
||||
if ($signatureFailure !== null) {
|
||||
return $signatureFailure;
|
||||
}
|
||||
|
||||
$payload = $this->request->getJSON(true);
|
||||
if (! is_array($payload) || empty($payload)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'ok' => 0,
|
||||
'error' => 'Invalid data push payload',
|
||||
]);
|
||||
}
|
||||
|
||||
$result = $this->service->ingestHealthInformationCallback($payload, 'hi_data_push_callback');
|
||||
|
||||
return $this->response->setStatusCode(202)->setJSON([
|
||||
'ok' => 1,
|
||||
'status' => 'accepted',
|
||||
'result' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
public function timeline()
|
||||
{
|
||||
$filters = [
|
||||
|
|
@ -328,4 +397,59 @@ class AbdmHiu extends BaseController
|
|||
{
|
||||
return preg_match('/^[A-Za-z0-9._-]+@[A-Za-z0-9.-]+$/', trim($value)) === 1;
|
||||
}
|
||||
|
||||
private function validateGatewayWebhookSignature(): ?\CodeIgniter\HTTP\ResponseInterface
|
||||
{
|
||||
$secret = trim((string) (
|
||||
$this->readRuntimeSetting('GATEWAY_TO_HMS_HMAC_SECRET')
|
||||
?: $this->readRuntimeSetting('EKA_WEBHOOK_SECRET')
|
||||
?: $this->readRuntimeSetting('HMS_WEBHOOK_SECRET')
|
||||
));
|
||||
|
||||
// If no secret is configured, accept payload to keep callback flow functional.
|
||||
if ($secret === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$rawBody = (string) $this->request->getBody();
|
||||
$provided = trim((string) (
|
||||
$this->request->getHeaderLine('X-Signature')
|
||||
?: $this->request->getHeaderLine('X-Hub-Signature-256')
|
||||
));
|
||||
|
||||
if ($provided === '') {
|
||||
return $this->response->setStatusCode(401)->setJSON([
|
||||
'ok' => 0,
|
||||
'error' => 'Missing webhook signature',
|
||||
]);
|
||||
}
|
||||
|
||||
$expected = hash_hmac('sha256', $rawBody, $secret);
|
||||
$normalizedProvided = preg_replace('/^sha256=/i', '', $provided) ?? '';
|
||||
|
||||
if (! hash_equals($expected, $normalizedProvided)) {
|
||||
return $this->response->setStatusCode(401)->setJSON([
|
||||
'ok' => 0,
|
||||
'error' => 'Invalid webhook signature',
|
||||
]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function readRuntimeSetting(string $name): string
|
||||
{
|
||||
if (! $this->db->tableExists('hospital_setting')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$row = $this->db->table('hospital_setting')
|
||||
->select('s_value')
|
||||
->where('s_name', $name)
|
||||
->orderBy('id', 'DESC')
|
||||
->get(1)
|
||||
->getRowArray();
|
||||
|
||||
return trim((string) ($row['s_value'] ?? ''));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1660,15 +1660,6 @@ class Opd extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
if ($abhaAddress !== '' && ! $this->isValidAbhaAddress($abhaAddress)) {
|
||||
return $this->response->setJSON([
|
||||
'insertid' => 0,
|
||||
'error_text' => 'Invalid ABHA Address format.',
|
||||
'csrfName' => csrf_token(),
|
||||
'csrfHash' => csrf_hash(),
|
||||
]);
|
||||
}
|
||||
|
||||
$sql = "select * from patient_master where id=" . $pid;
|
||||
$query = $this->db->query($sql);
|
||||
$personInfo = $query->getResult();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AdjustAbdmHiuWorkflowRequestOperationIndex extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_hiu_workflows')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$indexes = $this->db->query('SHOW INDEX FROM abdm_hiu_workflows')->getResultArray();
|
||||
$hasReqOpUnique = false;
|
||||
$hasReqOpIdx = false;
|
||||
|
||||
foreach ($indexes as $idx) {
|
||||
$name = (string) ($idx['Key_name'] ?? '');
|
||||
if ($name === 'uniq_abdm_hiu_req_op') {
|
||||
$hasReqOpUnique = true;
|
||||
}
|
||||
if ($name === 'idx_abdm_hiu_req_op') {
|
||||
$hasReqOpIdx = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasReqOpUnique) {
|
||||
$this->db->query('ALTER TABLE abdm_hiu_workflows DROP INDEX uniq_abdm_hiu_req_op');
|
||||
}
|
||||
|
||||
if (! $hasReqOpIdx) {
|
||||
$this->db->query('ALTER TABLE abdm_hiu_workflows ADD INDEX idx_abdm_hiu_req_op (request_id, operation)');
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_hiu_workflows')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$indexes = $this->db->query('SHOW INDEX FROM abdm_hiu_workflows')->getResultArray();
|
||||
$hasReqOpUnique = false;
|
||||
$hasReqOpIdx = false;
|
||||
|
||||
foreach ($indexes as $idx) {
|
||||
$name = (string) ($idx['Key_name'] ?? '');
|
||||
if ($name === 'uniq_abdm_hiu_req_op') {
|
||||
$hasReqOpUnique = true;
|
||||
}
|
||||
if ($name === 'idx_abdm_hiu_req_op') {
|
||||
$hasReqOpIdx = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasReqOpIdx) {
|
||||
$this->db->query('ALTER TABLE abdm_hiu_workflows DROP INDEX idx_abdm_hiu_req_op');
|
||||
}
|
||||
|
||||
if (! $hasReqOpUnique) {
|
||||
$this->db->query('ALTER TABLE abdm_hiu_workflows ADD UNIQUE INDEX uniq_abdm_hiu_req_op (request_id, operation)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,23 @@ class M3HiuGatewayClient
|
|||
$this->baseUrl = rtrim((string) (getenv('EATRIA_BRIDGE_URL') ?: 'https://abdm-bridge.e-atria.in/api'), '/');
|
||||
}
|
||||
|
||||
private function sanitizeBearerToken(string $token): string
|
||||
{
|
||||
$token = trim($token);
|
||||
if ($token === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (stripos($token, 'Bearer ') === 0) {
|
||||
$token = trim(substr($token, 7));
|
||||
}
|
||||
|
||||
$token = trim($token, " \t\n\r\0\x0B\"'");
|
||||
$token = (string) preg_replace('/\s+/u', '', $token);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function consentRequest(array $payload): array
|
||||
{
|
||||
return $this->call('/v3/hiu/consent/request', $payload, 'consent.request');
|
||||
|
|
@ -54,6 +71,10 @@ class M3HiuGatewayClient
|
|||
$token = (string) ($settings['token'] ?? '');
|
||||
$hfrId = (string) ($settings['hfr_id'] ?? '');
|
||||
$hospitalId = (string) ($settings['hospital_id'] ?? '');
|
||||
$runtimeBaseUrl = trim((string) ($settings['base_url'] ?? ''));
|
||||
if ($runtimeBaseUrl !== '') {
|
||||
$this->baseUrl = rtrim($runtimeBaseUrl, '/');
|
||||
}
|
||||
|
||||
$payloadHfrId = trim((string) ($payload['hfr_id'] ?? ''));
|
||||
if ($payloadHfrId !== '' && strcasecmp($payloadHfrId, $hfrId) !== 0) {
|
||||
|
|
@ -105,7 +126,7 @@ class M3HiuGatewayClient
|
|||
: ('HTTP ' . $httpCode . ' non-JSON response');
|
||||
}
|
||||
|
||||
if ($this->isCloudFrontBlockedResponse($httpCode, $raw, $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.';
|
||||
}
|
||||
|
||||
|
|
@ -178,6 +199,10 @@ class M3HiuGatewayClient
|
|||
$token = (string) ($settings['token'] ?? '');
|
||||
$hfrId = (string) ($settings['hfr_id'] ?? '');
|
||||
$hospitalId = (string) ($settings['hospital_id'] ?? '');
|
||||
$runtimeBaseUrl = trim((string) ($settings['base_url'] ?? ''));
|
||||
if ($runtimeBaseUrl !== '') {
|
||||
$this->baseUrl = rtrim($runtimeBaseUrl, '/');
|
||||
}
|
||||
|
||||
$payloadHfrId = trim((string) ($query['hfr_id'] ?? ''));
|
||||
if ($payloadHfrId !== '' && strcasecmp($payloadHfrId, $hfrId) !== 0) {
|
||||
|
|
@ -305,24 +330,46 @@ class M3HiuGatewayClient
|
|||
return ['ok' => 0, 'http_code' => 500, 'error_text' => 'hospital_setting table not found', 'retryable' => 0];
|
||||
}
|
||||
|
||||
$rows = $this->db->table('hospital_setting')
|
||||
$settingFields = $this->db->getFieldNames('hospital_setting') ?? [];
|
||||
$orderCol = null;
|
||||
foreach (['id', 's_id'] as $candidateOrderCol) {
|
||||
if (in_array($candidateOrderCol, $settingFields, true)) {
|
||||
$orderCol = $candidateOrderCol;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$rowsBuilder = $this->db->table('hospital_setting')
|
||||
->select('s_name, s_value')
|
||||
->whereIn('s_name', ['EATRIA_BRIDGE_TOKEN', 'ABDM_HFR_ID', 'ABDM_HOSPITAL_ID'])
|
||||
->get()
|
||||
->getResultArray();
|
||||
->whereIn('s_name', [
|
||||
'EATRIA_BRIDGE_TOKEN',
|
||||
'ABDM_HFR_ID',
|
||||
'H_HFR_ID',
|
||||
'ABDM_HOSPITAL_HFR_ID',
|
||||
'ABDM_HOSPITAL_ID',
|
||||
'ABDM_BRIDGE_HOSPITAL_ID',
|
||||
'EATRIA_BRIDGE_HOSPITAL_ID',
|
||||
'EATRIA_BRIDGE_URL',
|
||||
]);
|
||||
if ($orderCol !== null) {
|
||||
$rowsBuilder->orderBy($orderCol, 'DESC');
|
||||
}
|
||||
|
||||
$rows = $rowsBuilder->get()->getResultArray();
|
||||
|
||||
$kv = [];
|
||||
foreach ($rows as $row) {
|
||||
$key = trim((string) ($row['s_name'] ?? ''));
|
||||
if ($key === '') {
|
||||
if ($key === '' || array_key_exists($key, $kv)) {
|
||||
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'] ?? '');
|
||||
$token = $this->sanitizeBearerToken((string) ($kv['EATRIA_BRIDGE_TOKEN'] ?? ''));
|
||||
$hfrId = (string) ($kv['ABDM_HFR_ID'] ?? $kv['H_HFR_ID'] ?? $kv['ABDM_HOSPITAL_HFR_ID'] ?? '');
|
||||
$hospitalId = (string) ($kv['ABDM_HOSPITAL_ID'] ?? $kv['ABDM_BRIDGE_HOSPITAL_ID'] ?? $kv['EATRIA_BRIDGE_HOSPITAL_ID'] ?? '');
|
||||
$baseUrl = trim((string) ($kv['EATRIA_BRIDGE_URL'] ?? ''));
|
||||
|
||||
if ($token === '') {
|
||||
return [
|
||||
|
|
@ -346,6 +393,7 @@ class M3HiuGatewayClient
|
|||
'token' => $token,
|
||||
'hfr_id' => $hfrId,
|
||||
'hospital_id' => $hospitalId,
|
||||
'base_url' => $baseUrl,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -246,6 +246,7 @@ class M3HiuWorkflowService
|
|||
}
|
||||
|
||||
$builder = $this->db->table('abdm_hiu_workflows')->select('*')->orderBy('id', 'DESC');
|
||||
$fields = $this->db->getFieldNames('abdm_hiu_workflows') ?? [];
|
||||
|
||||
$hfrId = trim((string) ($filters['hfr_id'] ?? ''));
|
||||
if ($hfrId !== '') {
|
||||
|
|
@ -253,7 +254,16 @@ class M3HiuWorkflowService
|
|||
}
|
||||
$consentId = trim((string) ($filters['consent_id'] ?? ''));
|
||||
if ($consentId !== '') {
|
||||
$builder->groupStart();
|
||||
$builder->where('consent_id', $consentId);
|
||||
if (in_array('abdm_consent_request_id', $fields, true)) {
|
||||
$builder->orWhere('abdm_consent_request_id', $consentId);
|
||||
}
|
||||
if (in_array('abdm_consent_artifact_id', $fields, true)) {
|
||||
$builder->orWhere('abdm_consent_artifact_id', $consentId);
|
||||
}
|
||||
$builder->orWhere('request_id', $consentId);
|
||||
$builder->groupEnd();
|
||||
}
|
||||
$transactionId = trim((string) ($filters['transaction_id'] ?? ''));
|
||||
if ($transactionId !== '') {
|
||||
|
|
@ -261,7 +271,12 @@ class M3HiuWorkflowService
|
|||
}
|
||||
$abhaAddress = trim((string) ($filters['abha_address'] ?? ''));
|
||||
if ($abhaAddress !== '') {
|
||||
$builder->groupStart();
|
||||
$builder->where('abha_address', $abhaAddress);
|
||||
// Older rows may store ABHA address only inside request_json consent.patient.id.
|
||||
$builder->orLike('request_json', '"abha_address":"' . str_replace('"', '\\"', $abhaAddress) . '"');
|
||||
$builder->orLike('request_json', '"id":"' . str_replace('"', '\\"', $abhaAddress) . '"');
|
||||
$builder->groupEnd();
|
||||
}
|
||||
$from = trim((string) ($filters['date_from'] ?? ''));
|
||||
if ($from !== '') {
|
||||
|
|
@ -356,6 +371,188 @@ class M3HiuWorkflowService
|
|||
];
|
||||
}
|
||||
|
||||
public function ingestConsentUpdateWebhook(array $payload): array
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_hiu_workflows')) {
|
||||
return [
|
||||
'ok' => 1,
|
||||
'persisted' => 0,
|
||||
'message' => 'abdm_hiu_workflows table not found; callback accepted without persistence',
|
||||
];
|
||||
}
|
||||
|
||||
$consent = is_array($payload['consent'] ?? null) ? (array) $payload['consent'] : [];
|
||||
$consentDetail = is_array($payload['consentDetail'] ?? null) ? (array) $payload['consentDetail'] : [];
|
||||
$consentObj = ! empty($consent) ? $consent : $consentDetail;
|
||||
|
||||
$status = strtolower(trim((string) (
|
||||
$consentObj['status']
|
||||
?? $payload['consent_status']
|
||||
?? $payload['status']
|
||||
?? ''
|
||||
)));
|
||||
if ($status === '') {
|
||||
$status = 'status_checked';
|
||||
}
|
||||
|
||||
$consentId = trim((string) (
|
||||
$consentObj['id']
|
||||
?? $payload['consent_id']
|
||||
?? $payload['consentId']
|
||||
?? $payload['abdm_consent_artifact_id']
|
||||
?? ''
|
||||
));
|
||||
|
||||
$requestId = trim((string) (
|
||||
$payload['request_id']
|
||||
?? $payload['requestId']
|
||||
?? ($payload['response']['requestId'] ?? '')
|
||||
?? ''
|
||||
));
|
||||
$transactionId = trim((string) (
|
||||
$payload['transaction_id']
|
||||
?? $payload['transactionId']
|
||||
?? ($consentObj['transactionId'] ?? '')
|
||||
?? ''
|
||||
));
|
||||
|
||||
$abhaAddress = trim((string) (
|
||||
($consentObj['patient']['id'] ?? '')
|
||||
?: ($payload['abha_address'] ?? '')
|
||||
?: ($payload['abha_id'] ?? '')
|
||||
));
|
||||
$abhaAddress = $this->canonicalizeAbhaAddress($abhaAddress, (string) ($payload['abha_id'] ?? ''));
|
||||
|
||||
$persistPayload = [
|
||||
'request_id' => $requestId,
|
||||
'requestId' => $requestId,
|
||||
'transaction_id' => $transactionId,
|
||||
'transactionId' => $transactionId,
|
||||
'consent_id' => $consentId,
|
||||
'consentId' => $consentId,
|
||||
'abdm_consent_artifact_id' => $consentId,
|
||||
'abha_address' => $abhaAddress,
|
||||
'hfr_id' => trim((string) ($payload['hfr_id'] ?? '')),
|
||||
'consent' => $consentObj,
|
||||
'callback' => $payload,
|
||||
];
|
||||
|
||||
$result = [
|
||||
'ok' => 1,
|
||||
'http_code' => 202,
|
||||
'status' => $status,
|
||||
'consent_id' => $consentId,
|
||||
'consentId' => $consentId,
|
||||
'abdm_consent_artifact_id' => $consentId,
|
||||
'request_id' => $requestId,
|
||||
'transaction_id' => $transactionId,
|
||||
'abha_address' => $abhaAddress,
|
||||
'consent' => $consentObj,
|
||||
'data' => $payload,
|
||||
'retryable' => 0,
|
||||
];
|
||||
|
||||
$state = match ($status) {
|
||||
'granted', 'approved', 'active' => 'GRANTED',
|
||||
'revoked', 'denied' => 'REVOKED',
|
||||
'expired' => 'EXPIRED',
|
||||
default => 'STATUS_CHECKED',
|
||||
};
|
||||
|
||||
$this->upsertWorkflow('consent_callback', $persistPayload, $result, $state, 'success', 202, false);
|
||||
|
||||
return [
|
||||
'ok' => 1,
|
||||
'persisted' => 1,
|
||||
'workflow_state' => $state,
|
||||
'consent_id' => $consentId,
|
||||
'request_id' => $requestId,
|
||||
];
|
||||
}
|
||||
|
||||
public function ingestHealthInformationCallback(array $payload, string $operation = 'hi_on_request_callback'): array
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_hiu_workflows')) {
|
||||
return [
|
||||
'ok' => 1,
|
||||
'persisted' => 0,
|
||||
'message' => 'abdm_hiu_workflows table not found; callback accepted without persistence',
|
||||
];
|
||||
}
|
||||
|
||||
$requestId = trim((string) (
|
||||
$payload['request_id']
|
||||
?? $payload['requestId']
|
||||
?? ($payload['response']['requestId'] ?? '')
|
||||
?? ''
|
||||
));
|
||||
$transactionId = trim((string) (
|
||||
$payload['transaction_id']
|
||||
?? $payload['transactionId']
|
||||
?? ($payload['response']['transactionId'] ?? '')
|
||||
?? ''
|
||||
));
|
||||
$consentId = trim((string) (
|
||||
($payload['hiRequest']['consent']['id'] ?? '')
|
||||
?: ($payload['consent']['id'] ?? '')
|
||||
?: ($payload['consent_id'] ?? '')
|
||||
?: ($payload['consentId'] ?? '')
|
||||
));
|
||||
$abhaAddress = $this->canonicalizeAbhaAddress(trim((string) (
|
||||
($payload['patient']['id'] ?? '')
|
||||
?: ($payload['abha_address'] ?? '')
|
||||
?: ($payload['abha_id'] ?? '')
|
||||
)), (string) ($payload['abha_id'] ?? ''));
|
||||
|
||||
$errorCode = trim((string) ($payload['error']['code'] ?? ''));
|
||||
$errorMessage = trim((string) ($payload['error']['message'] ?? ''));
|
||||
$errorText = trim(($errorCode !== '' ? $errorCode . ' ' : '') . $errorMessage);
|
||||
|
||||
$hasData = isset($payload['fhir_bundle'])
|
||||
|| isset($payload['fhirBundle'])
|
||||
|| isset($payload['bundles'])
|
||||
|| isset($payload['entries'])
|
||||
|| isset($payload['data']);
|
||||
|
||||
$ok = $errorText === '' ? 1 : 0;
|
||||
$state = $hasData ? 'DATA_RECEIVED' : ($ok === 1 ? 'DATA_PENDING' : 'FAILED');
|
||||
|
||||
$persistPayload = [
|
||||
'request_id' => $requestId,
|
||||
'requestId' => $requestId,
|
||||
'transaction_id' => $transactionId,
|
||||
'transactionId' => $transactionId,
|
||||
'consent_id' => $consentId,
|
||||
'consentId' => $consentId,
|
||||
'abdm_consent_artifact_id' => $consentId,
|
||||
'abha_address' => $abhaAddress,
|
||||
'callback' => $payload,
|
||||
];
|
||||
$result = [
|
||||
'ok' => $ok,
|
||||
'http_code' => 202,
|
||||
'status' => strtolower($state),
|
||||
'error_text' => $errorText,
|
||||
'request_id' => $requestId,
|
||||
'transaction_id' => $transactionId,
|
||||
'consent_id' => $consentId,
|
||||
'abha_address' => $abhaAddress,
|
||||
'retryable' => 0,
|
||||
'data' => $payload,
|
||||
];
|
||||
|
||||
$this->upsertWorkflow($operation, $persistPayload, $result, $state, $ok === 1 ? 'success' : 'failed', 202, false);
|
||||
|
||||
return [
|
||||
'ok' => 1,
|
||||
'persisted' => 1,
|
||||
'workflow_state' => $state,
|
||||
'request_id' => $requestId,
|
||||
'transaction_id' => $transactionId,
|
||||
'error_text' => $errorText,
|
||||
];
|
||||
}
|
||||
|
||||
private function dispatch(string $operation, array $payload): array
|
||||
{
|
||||
return match ($operation) {
|
||||
|
|
@ -554,11 +751,14 @@ class M3HiuWorkflowService
|
|||
|
||||
if ($operation === 'consent_fetch') {
|
||||
$consentId = trim((string) ($clean['abdm_consent_artifact_id'] ?? $clean['consentId'] ?? $clean['consent_id'] ?? ''));
|
||||
if ($consentId === '') {
|
||||
$consentId = $this->resolveAbdmConsentArtifactIdFromWorkflow($clean);
|
||||
}
|
||||
if ($consentId === '') {
|
||||
return [
|
||||
'ok' => 0,
|
||||
'http_code' => 422,
|
||||
'error_text' => 'consentId is required for consent fetch.',
|
||||
'error_text' => 'consentId is required for consent fetch. Consent artifact is not available yet; run Check Consent Status until consent is GRANTED, then fetch using artifact id.',
|
||||
];
|
||||
}
|
||||
if ($this->isGatewayRequestIdPattern($consentId)) {
|
||||
|
|
@ -611,10 +811,11 @@ class M3HiuWorkflowService
|
|||
|
||||
if (! isset($hiRequest['dateRange']) || ! is_array($hiRequest['dateRange'])) {
|
||||
$hiRequest['dateRange'] = [
|
||||
'from' => gmdate('Y-m-d\T00:00:00.000\Z', strtotime('-30 days')),
|
||||
'from' => gmdate('Y-m-d\T00:00:00.000\Z', strtotime('-365 days')),
|
||||
'to' => gmdate('Y-m-d\T00:00:00.000\Z'),
|
||||
];
|
||||
}
|
||||
$hiRequest['dateRange'] = $this->resolveSafeHiDateRange($requestIdRef, $consentId, $hiRequest['dateRange']);
|
||||
if (trim((string) ($hiRequest['dataPushUrl'] ?? '')) === '') {
|
||||
$hiRequest['dataPushUrl'] = rtrim((string) (getenv('EATRIA_BRIDGE_URL') ?: 'https://abdm-bridge.e-atria.in/api'), '/') . '/v3/hiu/data/push';
|
||||
}
|
||||
|
|
@ -743,7 +944,7 @@ class M3HiuWorkflowService
|
|||
'permission' => [
|
||||
'accessMode' => 'VIEW',
|
||||
'dateRange' => [
|
||||
'from' => gmdate('Y-m-d\T00:00:00.000\Z', strtotime('-30 days')),
|
||||
'from' => gmdate('Y-m-d\T00:00:00.000\Z', strtotime('-365 days')),
|
||||
'to' => gmdate('Y-m-d\T00:00:00.000\Z'),
|
||||
],
|
||||
'dataEraseAt' => gmdate('Y-m-d\T00:00:00.000\Z', strtotime('+365 days')),
|
||||
|
|
@ -793,15 +994,257 @@ class M3HiuWorkflowService
|
|||
'consent' => [
|
||||
'id' => $consentId,
|
||||
],
|
||||
'dateRange' => [
|
||||
'from' => gmdate('Y-m-d\T00:00:00.000\Z', strtotime('-30 days')),
|
||||
'dateRange' => $this->resolveSafeHiDateRange($requestIdRef, $consentId, [
|
||||
'from' => gmdate('Y-m-d\T00:00:00.000\Z', strtotime('-365 days')),
|
||||
'to' => gmdate('Y-m-d\T00:00:00.000\Z'),
|
||||
],
|
||||
]),
|
||||
'dataPushUrl' => rtrim((string) (getenv('EATRIA_BRIDGE_URL') ?: 'https://abdm-bridge.e-atria.in/api'), '/') . '/v3/hiu/data/push',
|
||||
'transactionId' => $transactionId,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a safe HI date range by enforcing consent-granted permission window
|
||||
* (from consent fetch/reconcile details), then clamping with from <= to <= now.
|
||||
*
|
||||
* @param array<string, mixed> $fallbackRange
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function resolveSafeHiDateRange(string $requestIdRef, string $consentId, array $fallbackRange): array
|
||||
{
|
||||
$grantedRange = $this->resolveGrantedConsentDateRange($requestIdRef, $consentId);
|
||||
$consentRange = ! empty($grantedRange)
|
||||
? $grantedRange
|
||||
: $this->resolveConsentRequestDateRangeByRequestId($requestIdRef);
|
||||
|
||||
$fromRaw = trim((string) ($consentRange['from'] ?? $fallbackRange['from'] ?? ''));
|
||||
$toRaw = trim((string) ($consentRange['to'] ?? $fallbackRange['to'] ?? ''));
|
||||
|
||||
$nowMs = (int) floor(microtime(true) * 1000);
|
||||
$fromMs = $this->parseIsoUtcToEpochMillis($fromRaw);
|
||||
$toMs = $this->parseIsoUtcToEpochMillis($toRaw);
|
||||
$grantedFromMs = $this->parseIsoUtcToEpochMillis((string) ($grantedRange['from'] ?? ''));
|
||||
$grantedToMs = $this->parseIsoUtcToEpochMillis((string) ($grantedRange['to'] ?? ''));
|
||||
|
||||
if ($fromMs === null) {
|
||||
$fromMs = $nowMs - (365 * 24 * 60 * 60 * 1000);
|
||||
}
|
||||
if ($toMs === null) {
|
||||
$toMs = $nowMs;
|
||||
}
|
||||
|
||||
// HMS-side requirement: keep HI range equal to or strictly within granted consent permission range.
|
||||
if ($grantedFromMs !== null && $fromMs < $grantedFromMs) {
|
||||
$fromMs = $grantedFromMs;
|
||||
}
|
||||
if ($grantedToMs !== null && $toMs > $grantedToMs) {
|
||||
$toMs = $grantedToMs;
|
||||
}
|
||||
|
||||
if ($toMs > $nowMs) {
|
||||
$toMs = $nowMs;
|
||||
}
|
||||
if ($fromMs > $toMs) {
|
||||
if ($grantedFromMs !== null && $grantedFromMs <= $toMs) {
|
||||
$fromMs = $grantedFromMs;
|
||||
} else {
|
||||
$fromMs = $toMs;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'from' => $this->formatEpochMillisToIsoUtc($fromMs),
|
||||
'to' => $this->formatEpochMillisToIsoUtc($toMs),
|
||||
];
|
||||
}
|
||||
|
||||
private function parseIsoUtcToEpochMillis(string $value): ?int
|
||||
{
|
||||
$raw = trim($value);
|
||||
if ($raw === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$dt = new \DateTimeImmutable($raw);
|
||||
} catch (\Throwable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sec = (int) $dt->format('U');
|
||||
$micro = (int) $dt->format('u');
|
||||
|
||||
return ($sec * 1000) + intdiv($micro, 1000);
|
||||
}
|
||||
|
||||
private function formatEpochMillisToIsoUtc(int $epochMs): string
|
||||
{
|
||||
$sec = intdiv($epochMs, 1000);
|
||||
$ms = $epochMs - ($sec * 1000);
|
||||
if ($ms < 0) {
|
||||
$ms = 0;
|
||||
}
|
||||
|
||||
$dt = (new \DateTimeImmutable('@' . $sec))->setTimezone(new \DateTimeZone('UTC'));
|
||||
|
||||
return $dt->format('Y-m-d\TH:i:s') . '.' . str_pad((string) $ms, 3, '0', STR_PAD_LEFT) . 'Z';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{from?: string, to?: string}
|
||||
*/
|
||||
private function resolveGrantedConsentDateRange(string $requestId, string $consentId): array
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_hiu_workflows')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('abdm_hiu_workflows') ?? [];
|
||||
$builder = $this->db->table('abdm_hiu_workflows')
|
||||
->select('response_json, request_json')
|
||||
->whereIn('operation', ['consent_fetch', 'consent_reconcile', 'consent_status', 'consent_callback']);
|
||||
|
||||
$hasFilter = false;
|
||||
$builder->groupStart();
|
||||
if ($requestId !== '') {
|
||||
$builder->orWhere('request_id', $requestId);
|
||||
$hasFilter = true;
|
||||
}
|
||||
if ($consentId !== '') {
|
||||
$builder->orWhere('consent_id', $consentId);
|
||||
$hasFilter = true;
|
||||
if (in_array('abdm_consent_artifact_id', $fields, true)) {
|
||||
$builder->orWhere('abdm_consent_artifact_id', $consentId);
|
||||
}
|
||||
}
|
||||
$builder->groupEnd();
|
||||
|
||||
if (! $hasFilter) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = $builder->orderBy('id', 'DESC')->get(30)->getResultArray();
|
||||
foreach ($rows as $row) {
|
||||
foreach (['response_json', 'request_json'] as $jsonCol) {
|
||||
$decoded = json_decode((string) ($row[$jsonCol] ?? ''), true);
|
||||
if (! is_array($decoded)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$range = $this->extractPermissionDateRange($decoded);
|
||||
if (! empty($range)) {
|
||||
return $range;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @return array{from?: string, to?: string}
|
||||
*/
|
||||
private function extractPermissionDateRange(array $data): array
|
||||
{
|
||||
$pathCandidates = [
|
||||
$data['consent']['permission']['dateRange'] ?? null,
|
||||
$data['consentDetail']['permission']['dateRange'] ?? null,
|
||||
$data['data']['consent']['permission']['dateRange'] ?? null,
|
||||
$data['data']['consentDetail']['permission']['dateRange'] ?? null,
|
||||
$data['response']['consent']['permission']['dateRange'] ?? null,
|
||||
$data['response']['consentDetail']['permission']['dateRange'] ?? null,
|
||||
];
|
||||
|
||||
foreach ($pathCandidates as $candidate) {
|
||||
if (is_array($candidate)) {
|
||||
$from = trim((string) ($candidate['from'] ?? ''));
|
||||
$to = trim((string) ($candidate['to'] ?? ''));
|
||||
if ($from !== '' || $to !== '') {
|
||||
$out = [];
|
||||
if ($from !== '') {
|
||||
$out['from'] = $from;
|
||||
}
|
||||
if ($to !== '') {
|
||||
$out['to'] = $to;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['permission']) && is_array($data['permission'])) {
|
||||
$range = $data['permission']['dateRange'] ?? null;
|
||||
if (is_array($range)) {
|
||||
$from = trim((string) ($range['from'] ?? ''));
|
||||
$to = trim((string) ($range['to'] ?? ''));
|
||||
if ($from !== '' || $to !== '') {
|
||||
$out = [];
|
||||
if ($from !== '') {
|
||||
$out['from'] = $from;
|
||||
}
|
||||
if ($to !== '') {
|
||||
$out['to'] = $to;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data as $value) {
|
||||
if (! is_array($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nested = $this->extractPermissionDateRange($value);
|
||||
if (! empty($nested)) {
|
||||
return $nested;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{from?: string, to?: string}
|
||||
*/
|
||||
private function resolveConsentRequestDateRangeByRequestId(string $requestId): array
|
||||
{
|
||||
if ($requestId === '' || ! $this->db->tableExists('abdm_hiu_workflows')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$row = $this->db->table('abdm_hiu_workflows')
|
||||
->select('request_json')
|
||||
->where('operation', 'consent_request')
|
||||
->where('request_id', $requestId)
|
||||
->orderBy('id', 'DESC')
|
||||
->get(1)
|
||||
->getRowArray();
|
||||
|
||||
if (empty($row['request_json'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode((string) $row['request_json'], true);
|
||||
if (! is_array($decoded)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$from = trim((string) ($decoded['consent']['permission']['dateRange']['from'] ?? ''));
|
||||
$to = trim((string) ($decoded['consent']['permission']['dateRange']['to'] ?? ''));
|
||||
|
||||
$out = [];
|
||||
if ($from !== '') {
|
||||
$out['from'] = $from;
|
||||
}
|
||||
if ($to !== '') {
|
||||
$out['to'] = $to;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function readSettingValue(string $key): string
|
||||
{
|
||||
if (! $this->db->tableExists('hospital_setting')) {
|
||||
|
|
@ -937,7 +1380,10 @@ class M3HiuWorkflowService
|
|||
'consent_request' => 'REQUESTED',
|
||||
'consent_status' => 'STATUS_CHECKED',
|
||||
'consent_fetch' => 'CONSENT_FETCHED',
|
||||
'consent_callback' => ($statusStr === 'granted' || $statusStr === 'approved' ? 'GRANTED' : ($statusStr === 'revoked' ? 'REVOKED' : ($statusStr === 'expired' ? 'EXPIRED' : 'STATUS_CHECKED'))),
|
||||
'hi_request' => ($statusStr === 'completed' ? 'COMPLETED' : 'DATA_REQUESTED'),
|
||||
'hi_on_request_callback' => (($statusStr === 'data_received' || isset($result['fhir_bundle']) || isset($result['bundles']) || isset($result['entries'])) ? 'DATA_RECEIVED' : ($ok ? 'DATA_PENDING' : 'FAILED')),
|
||||
'hi_data_push_callback' => (($statusStr === 'data_received' || isset($result['fhir_bundle']) || isset($result['bundles']) || isset($result['entries'])) ? 'DATA_RECEIVED' : ($ok ? 'DATA_PENDING' : 'FAILED')),
|
||||
'consent_reconcile' => ($statusStr === 'granted' ? 'GRANTED' : ($statusStr === 'revoked' ? 'REVOKED' : ($statusStr === 'expired' ? 'EXPIRED' : 'STATUS_CHECKED'))),
|
||||
'data_fetch' => (($statusStr === 'completed' || isset($result['fhir_bundle']) || isset($result['bundles'])) ? 'DATA_RECEIVED' : 'DATA_PENDING'),
|
||||
default => 'COMPLETED',
|
||||
|
|
@ -949,6 +1395,47 @@ class M3HiuWorkflowService
|
|||
$requestId = trim((string) ($payload['request_id'] ?? ''));
|
||||
$transactionId = trim((string) ($payload['transaction_id'] ?? ''));
|
||||
|
||||
// HI operations should dedupe by transaction id so retries/new requests
|
||||
// keep separate timeline rows instead of overwriting prior transactions.
|
||||
if (in_array($operation, ['hi_request', 'data_fetch'], true)) {
|
||||
if ($transactionId === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->db->table('abdm_hiu_workflows')
|
||||
->select('*')
|
||||
->where('operation', $operation)
|
||||
->where('transaction_id', $transactionId)
|
||||
->orderBy('id', 'DESC')
|
||||
->get(1)
|
||||
->getRowArray() ?: null;
|
||||
}
|
||||
|
||||
if (in_array($operation, ['hi_on_request_callback', 'hi_data_push_callback'], true)) {
|
||||
$builder = $this->db->table('abdm_hiu_workflows')->select('*')->where('operation', $operation);
|
||||
if ($transactionId !== '') {
|
||||
$row = $builder->where('transaction_id', $transactionId)->orderBy('id', 'DESC')->get(1)->getRowArray();
|
||||
if (! empty($row)) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
if ($requestId !== '') {
|
||||
$row = $this->db->table('abdm_hiu_workflows')
|
||||
->select('*')
|
||||
->where('operation', $operation)
|
||||
->where('request_id', $requestId)
|
||||
->orderBy('id', 'DESC')
|
||||
->get(1)
|
||||
->getRowArray();
|
||||
if (! empty($row)) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('abdm_hiu_workflows')->select('*')->where('operation', $operation);
|
||||
if ($requestId !== '') {
|
||||
$row = $builder->where('request_id', $requestId)->orderBy('id', 'DESC')->get(1)->getRowArray();
|
||||
|
|
@ -1089,21 +1576,67 @@ class M3HiuWorkflowService
|
|||
int $forceId = 0
|
||||
): void {
|
||||
$now = Time::now('Asia/Kolkata')->toDateTimeString();
|
||||
$hmsRequestId = trim((string) ($payload['request_id'] ?? $payload['requestId'] ?? ''));
|
||||
$hmsRequestId = $this->firstNonEmptyString([
|
||||
$payload['request_id'] ?? null,
|
||||
$payload['requestId'] ?? null,
|
||||
]);
|
||||
if ($hmsRequestId === '') {
|
||||
$hmsRequestId = trim((string) ($result['hms_request_id'] ?? ''));
|
||||
$hmsRequestId = $this->firstNonEmptyString([
|
||||
$result['hms_request_id'] ?? null,
|
||||
]);
|
||||
}
|
||||
$gatewayRequestId = trim((string) ($result['gateway_request_id'] ?? $result['request_id'] ?? $result['requestId'] ?? ''));
|
||||
$abdmConsentRequestId = trim((string) ($result['abdm_consent_request_id'] ?? $result['consent_request_id'] ?? $payload['consentRequestId'] ?? $payload['consent_request_id'] ?? ''));
|
||||
$transactionId = trim((string) ($payload['transaction_id'] ?? $payload['transactionId'] ?? $result['transaction_id'] ?? ''));
|
||||
$consentId = trim((string) ($payload['abdm_consent_artifact_id'] ?? $payload['consent_id'] ?? $payload['consentId'] ?? $result['abdm_consent_artifact_id'] ?? $result['consent_id'] ?? $result['consentId'] ?? ''));
|
||||
$gatewayRequestId = $this->firstNonEmptyString([
|
||||
$result['gateway_request_id'] ?? null,
|
||||
$result['request_id'] ?? null,
|
||||
$result['requestId'] ?? null,
|
||||
]);
|
||||
$abdmConsentRequestId = $this->firstNonEmptyString([
|
||||
$result['abdm_consent_request_id'] ?? null,
|
||||
$result['consent_request_id'] ?? null,
|
||||
$payload['consentRequestId'] ?? null,
|
||||
$payload['consent_request_id'] ?? null,
|
||||
]);
|
||||
$transactionId = $this->firstNonEmptyString([
|
||||
$payload['transaction_id'] ?? null,
|
||||
$payload['transactionId'] ?? null,
|
||||
$result['transaction_id'] ?? null,
|
||||
$result['transactionId'] ?? null,
|
||||
]);
|
||||
$consentId = $this->firstNonEmptyString([
|
||||
$payload['abdm_consent_artifact_id'] ?? null,
|
||||
$payload['consent_id'] ?? null,
|
||||
$payload['consentId'] ?? null,
|
||||
$payload['hiRequest']['consent']['id'] ?? null,
|
||||
$result['abdm_consent_artifact_id'] ?? null,
|
||||
$result['consent_id'] ?? null,
|
||||
$result['consentId'] ?? null,
|
||||
$result['hiRequest']['consent']['id'] ?? null,
|
||||
]);
|
||||
if ($consentId === '' && $operation !== 'consent_fetch' && $operation !== 'hi_request') {
|
||||
$consentId = $abdmConsentRequestId;
|
||||
}
|
||||
$abhaAddress = trim((string) ($payload['abha_address'] ?? $payload['abha_id'] ?? $result['abha_address'] ?? ''));
|
||||
$hfrId = $this->normalizeStoredHfrId((string) ($payload['hfr_id'] ?? $result['hfr_id'] ?? ''));
|
||||
$hospitalId = trim((string) ($payload['hospital_id'] ?? $result['hospital_id'] ?? ''));
|
||||
$errorText = trim((string) ($result['error_text'] ?? $result['message'] ?? ''));
|
||||
$abhaAddress = $this->firstNonEmptyString([
|
||||
$payload['abha_address'] ?? null,
|
||||
$payload['abha_id'] ?? null,
|
||||
$result['abha_address'] ?? null,
|
||||
]);
|
||||
if ($abhaAddress === '') {
|
||||
$abhaAddress = $this->firstNonEmptyString([
|
||||
$payload['consent']['patient']['id'] ?? null,
|
||||
]);
|
||||
}
|
||||
$hfrId = $this->normalizeStoredHfrId($this->firstNonEmptyString([
|
||||
$payload['hfr_id'] ?? null,
|
||||
$result['hfr_id'] ?? null,
|
||||
]));
|
||||
$hospitalId = $this->firstNonEmptyString([
|
||||
$payload['hospital_id'] ?? null,
|
||||
$result['hospital_id'] ?? null,
|
||||
]);
|
||||
$errorText = $this->firstNonEmptyString([
|
||||
$result['error_text'] ?? null,
|
||||
$result['message'] ?? null,
|
||||
]);
|
||||
|
||||
$retryCount = 0;
|
||||
$id = $forceId;
|
||||
|
|
@ -1185,6 +1718,21 @@ class M3HiuWorkflowService
|
|||
return preg_match('/^REQ-/i', trim($value)) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $values
|
||||
*/
|
||||
private function firstNonEmptyString(array $values): string
|
||||
{
|
||||
foreach ($values as $value) {
|
||||
$text = trim((string) ($value ?? ''));
|
||||
if ($text !== '') {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function resolveAbdmConsentRequestIdFromWorkflow(array $payload): string
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_hiu_workflows')) {
|
||||
|
|
@ -1262,4 +1810,82 @@ class M3HiuWorkflowService
|
|||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function resolveAbdmConsentArtifactIdFromWorkflow(array $payload): string
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_hiu_workflows')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('abdm_hiu_workflows') ?? [];
|
||||
$hasAbdmConsentArtifactId = in_array('abdm_consent_artifact_id', $fields, true);
|
||||
|
||||
$requestId = trim((string) ($payload['requestId'] ?? $payload['request_id'] ?? ''));
|
||||
$transactionId = trim((string) ($payload['transactionId'] ?? $payload['transaction_id'] ?? ''));
|
||||
$abhaAddress = trim((string) ($payload['abha_address'] ?? $payload['abha_id'] ?? ''));
|
||||
|
||||
$tryBuilder = function (?string $whereCol, string $whereVal) use ($hasAbdmConsentArtifactId): string {
|
||||
if ($whereCol === null || $whereVal === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$select = ['consent_id', 'response_json'];
|
||||
if ($hasAbdmConsentArtifactId) {
|
||||
$select[] = 'abdm_consent_artifact_id';
|
||||
}
|
||||
|
||||
$rows = $this->db->table('abdm_hiu_workflows')
|
||||
->select(implode(', ', $select))
|
||||
->where($whereCol, $whereVal)
|
||||
->whereIn('operation', ['consent_fetch', 'consent_reconcile', 'consent_status', 'hi_request'])
|
||||
->orderBy('id', 'DESC')
|
||||
->get(25)
|
||||
->getResultArray();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$candidate = '';
|
||||
if ($hasAbdmConsentArtifactId) {
|
||||
$candidate = trim((string) ($row['abdm_consent_artifact_id'] ?? ''));
|
||||
}
|
||||
if ($candidate === '') {
|
||||
$candidate = trim((string) ($row['consent_id'] ?? ''));
|
||||
}
|
||||
if ($candidate !== '' && ! $this->isGatewayRequestIdPattern($candidate)) {
|
||||
return $candidate;
|
||||
}
|
||||
|
||||
$decoded = json_decode((string) ($row['response_json'] ?? ''), true);
|
||||
if (is_array($decoded)) {
|
||||
$candidate = trim((string) (
|
||||
$decoded['abdm_consent_artifact_id']
|
||||
?? $decoded['consent_id']
|
||||
?? $decoded['consentId']
|
||||
?? ''
|
||||
));
|
||||
if ($candidate !== '' && ! $this->isGatewayRequestIdPattern($candidate)) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
$resolved = $tryBuilder('request_id', $requestId);
|
||||
if ($resolved !== '') {
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
$resolved = $tryBuilder('transaction_id', $transactionId);
|
||||
if ($resolved !== '') {
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
$resolved = $tryBuilder('abha_address', $abhaAddress);
|
||||
if ($resolved !== '') {
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -30,12 +30,6 @@
|
|||
<input class="form-control" id="appointment_time" name="appointment_time"
|
||||
type="time" value="<?= date('H:i') ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">ABHA Address</label>
|
||||
<input class="form-control" id="abha_address" name="abha_address" type="text"
|
||||
maxlength="18" value="<?= esc($person_info[0]->abha_address ?? $person_info[0]->abha ?? '') ?>"
|
||||
placeholder="e.g. ravi1234 or ravi.1234">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mt-2" id="ShowDoctor">
|
||||
|
|
@ -95,7 +89,7 @@
|
|||
|
||||
$('#showfee').hide();
|
||||
|
||||
$('#btnnextfee').click(function() {
|
||||
$('#btnnextfee').off('click.opdAppt').on('click.opdAppt', function() {
|
||||
var checkValue = $('#rdoc_id:checked').val();
|
||||
if (!checkValue) {
|
||||
alert('Please Select Doctor Name');
|
||||
|
|
@ -117,12 +111,12 @@
|
|||
$('#ShowDoctor').hide();
|
||||
});
|
||||
|
||||
$('#backtodoc').click(function() {
|
||||
$('#backtodoc').off('click.opdAppt').on('click.opdAppt', function() {
|
||||
$('#ShowDoctor').show();
|
||||
$('#showfee').hide();
|
||||
});
|
||||
|
||||
$('#btnnextconfirm').click(function() {
|
||||
$('#btnnextconfirm').off('click.opdAppt').on('click.opdAppt', function() {
|
||||
var checkValue = $('#fee_id:checked').val();
|
||||
if (!checkValue) {
|
||||
alert('Please Select Fee Name');
|
||||
|
|
@ -138,7 +132,6 @@
|
|||
"pid": $('#pid').val(),
|
||||
"datepicker_appointment": $('#datepicker_appointment').val(),
|
||||
"appointment_time": $('#appointment_time').val(),
|
||||
"abha_address": $('#abha_address').val(),
|
||||
[csrf.name]: csrf.value
|
||||
}, function(data) {
|
||||
updateCsrf(data);
|
||||
|
|
|
|||
|
|
@ -255,12 +255,8 @@
|
|||
delete_varible();
|
||||
}
|
||||
$("#wait").css("display", "none");
|
||||
var mainEl = document.getElementById('main');
|
||||
if (mainEl) {
|
||||
mainEl.innerHTML = html;
|
||||
} else {
|
||||
$("#main").html(html);
|
||||
}
|
||||
// Use jQuery html() so old node handlers/data are cleaned up.
|
||||
$("#main").html(html);
|
||||
executeInjectedScripts('main');
|
||||
if (typeof initfunc === 'function') {
|
||||
initfunc();
|
||||
|
|
@ -306,12 +302,8 @@
|
|||
timeout: REQUEST_TIMEOUT_MS
|
||||
})
|
||||
.done(function(html) {
|
||||
var targetEl = document.getElementById(xdiv);
|
||||
if (targetEl) {
|
||||
targetEl.innerHTML = html;
|
||||
} else {
|
||||
$("#" + xdiv).html(html);
|
||||
}
|
||||
// Use jQuery html() so old node handlers/data are cleaned up.
|
||||
$("#" + xdiv).html(html);
|
||||
executeInjectedScripts(xdiv);
|
||||
if (typeof initfunc === 'function') {
|
||||
initfunc();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user