From a7727c9d9e2ac7c86d3881971ad1cf3faaa1cd58 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Mon, 24 Aug 2026 23:19:21 +0530 Subject: [PATCH] fix(abdm): correct mobile ABHA discovery flow Route Find ABHA via Mobile through standalone bridge OTP endpoints and remove exact placeholder values from generated FHIR bundles. --- app/Config/Routes.php | 2 + app/Controllers/Abha.php | 18 +++++-- .../Abdm/Fhir/FhirDocumentBuilder.php | 12 ++++- app/Libraries/FhirR4Builder.php | 52 +++++++++++++++---- app/Views/billing/Patient_V.php | 13 +++-- app/Views/partials/abha_create_panel.php | 4 +- app/Views/partials/abha_mobile_modal.php | 10 ++-- tests/unit/Abdm/FhirGeneratorsTest.php | 33 ++++++++++++ 8 files changed, 117 insertions(+), 27 deletions(-) diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 9558ec9..2ee7994 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -774,6 +774,8 @@ $routes->post('abha/create/initiate', 'Abha::initiate', ['filter' => 'permission $routes->post('abha/create/verify_otp', 'Abha::verifyOtp', ['filter' => 'permission:abdm.abha.create']); $routes->post('abha/create/communication', 'Abha::communication', ['filter' => 'permission:abdm.abha.create']); $routes->post('abha/create/verify_comm_otp', 'Abha::verifyCommOtp', ['filter' => 'permission:abdm.abha.create']); +$routes->post('abha/find/mobile/request-otp', 'Abha::communication', ['filter' => 'permission:abdm.abha.create']); +$routes->post('abha/find/mobile/verify-otp', 'Abha::verifyCommOtp', ['filter' => 'permission:abdm.abha.create']); $routes->post('abha/create/confirm_patient', 'Abha::confirmPatient', ['filter' => 'permission:abdm.abha.create']); $routes->post('abha/create/address', 'Abha::address', ['filter' => 'permission:abdm.abha.create']); $routes->post('abha/create/address_suggestions', 'Abha::addressSuggestions', ['filter' => 'permission:abdm.abha.create']); diff --git a/app/Controllers/Abha.php b/app/Controllers/Abha.php index e0ab82d..c1a0a8f 100644 --- a/app/Controllers/Abha.php +++ b/app/Controllers/Abha.php @@ -233,17 +233,20 @@ class Abha extends BaseController $mobile = preg_replace('/\D/', '', trim((string) ($this->request->getPost('mobile') ?? ''))); $txnId = trim((string) ($this->request->getPost('txn_id') ?? $this->request->getPost('txnId') ?? '')); + $isMobileDiscovery = (string) $this->request->getPost('find_abha') === '1'; if (strlen($mobile) !== 10) { return $this->response->setJSON(['ok' => 0, 'error_text' => 'Valid 10-digit mobile number is required']); } - if ($txnId === '') { + if (! $isMobileDiscovery && $txnId === '') { return $this->response->setJSON(['ok' => 0, 'error_text' => 'Aadhaar enrolment txn_id is required']); } try { - $result = AbdmConnectorFactory::make()->abhaEnrolMobileRequestOtp(['mobile' => $mobile, 'txnId' => $txnId]); + $result = $isMobileDiscovery + ? AbdmConnectorFactory::make()->abhaMobileGenerateOtp(['mobile' => $mobile]) + : AbdmConnectorFactory::make()->abhaEnrolMobileRequestOtp(['mobile' => $mobile, 'txnId' => $txnId]); } catch (\Throwable $e) { return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]); } @@ -281,6 +284,7 @@ class Abha extends BaseController $txnId = trim((string) ($this->request->getPost('txn_id') ?? $this->request->getPost('txnId') ?? '')); $otp = trim((string) ($this->request->getPost('otp') ?? '')); + $isMobileDiscovery = (string) $this->request->getPost('find_abha') === '1'; // The mobile itself isn't part of the OTP-verify payload (it's tied to txn_id // on the bridge side), but the caller may resend it so we can persist it even // if the gateway response doesn't echo it back. @@ -291,7 +295,9 @@ class Abha extends BaseController } try { - $result = AbdmConnectorFactory::make()->abhaEnrolMobileVerifyOtp(['txnId' => $txnId, 'otp' => $otp]); + $result = $isMobileDiscovery + ? AbdmConnectorFactory::make()->abhaMobileVerifyOtp(['txnId' => $txnId, 'otp' => $otp]) + : AbdmConnectorFactory::make()->abhaEnrolMobileVerifyOtp(['txnId' => $txnId, 'otp' => $otp]); } catch (\Throwable $e) { return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]); } @@ -319,7 +325,7 @@ class Abha extends BaseController $abhaNum = (string) ($profile['ABHANumber'] ?? $profile['abha_id'] ?? $payload['ABHANumber'] ?? $payload['abha_id'] ?? ''); $expectedAbha = preg_replace('/\D/', '', (string) (session()->get('abha_enrol_identity_' . $txnId) ?? '')); $returnedAbha = preg_replace('/\D/', '', $abhaNum); - if (strlen($expectedAbha) !== 14 || $returnedAbha !== $expectedAbha) { + if (! $isMobileDiscovery && (strlen($expectedAbha) !== 14 || $returnedAbha !== $expectedAbha)) { log_message('error', '[ABHA] Enrolment identity mismatch blocked for txn ' . $txnId); return $this->response->setStatusCode(409)->setJSON([ 'ok' => 0, @@ -327,7 +333,9 @@ class Abha extends BaseController 'request_id' => (string) ($result['request_id'] ?? ''), ]); } - session()->remove('abha_enrol_identity_' . $txnId); + if (! $isMobileDiscovery) { + session()->remove('abha_enrol_identity_' . $txnId); + } $abhaAddress = (string) ( ($profile['preferredAddress'] ?? '') ?: ($profile['preferredAbhaAddress'] ?? '') diff --git a/app/Libraries/Abdm/Fhir/FhirDocumentBuilder.php b/app/Libraries/Abdm/Fhir/FhirDocumentBuilder.php index 0bedc65..ccc182b 100644 --- a/app/Libraries/Abdm/Fhir/FhirDocumentBuilder.php +++ b/app/Libraries/Abdm/Fhir/FhirDocumentBuilder.php @@ -293,7 +293,7 @@ class FhirDocumentBuilder { $clean = []; foreach ($value as $k => $v) { - if ($v === null || $v === '') { + if ($v === null || $v === '' || $this->isPlaceholderValue($v)) { continue; } if (is_array($v)) { @@ -310,6 +310,16 @@ class FhirDocumentBuilder return $clean === [] ? null : $clean; } + /** @param mixed $value */ + private function isPlaceholderValue($value): bool + { + if (! is_string($value)) { + return false; + } + + return in_array(strtoupper(trim($value)), ['NA', 'N/A', 'NULL', 'NOT AVAILABLE'], true); + } + private function newId(string $prefix): string { $prefix = strtolower(preg_replace('/[^a-z0-9]/i', '-', $prefix) ?: 'resource'); diff --git a/app/Libraries/FhirR4Builder.php b/app/Libraries/FhirR4Builder.php index fa76583..5e6007d 100644 --- a/app/Libraries/FhirR4Builder.php +++ b/app/Libraries/FhirR4Builder.php @@ -665,7 +665,7 @@ class FhirR4Builder ? 'OPD-' . ($encounter['id'] ?? $bundleUuid) . '-' . date('Y-m-d') : $bundleUuid; - return [ + return $this->sanitizeBundle([ 'resourceType' => 'Bundle', 'id' => $bundleUuid, 'meta' => [ @@ -680,7 +680,7 @@ class FhirR4Builder 'type' => 'document', 'timestamp' => $issuedAt, 'entry' => $allEntries, - ]; + ]); } /** @@ -1110,7 +1110,7 @@ class FhirR4Builder ? 'LAB-' . (string) ($diagnosticReport['id'] ?? $bundleUuid) : $bundleUuid; - return [ + return $this->sanitizeBundle([ 'resourceType' => 'Bundle', 'id' => $bundleUuid, 'meta' => [ @@ -1125,7 +1125,7 @@ class FhirR4Builder $resourceEntries, $observationEntries ), - ]; + ]); } /** @@ -1392,7 +1392,7 @@ class FhirR4Builder ? 'IPD-' . ($ipdCode !== '' ? $ipdCode : $bundleUuid) : $bundleUuid; - return [ + return $this->sanitizeBundle([ 'resourceType' => 'Bundle', 'id' => $bundleUuid, 'meta' => [ @@ -1406,7 +1406,7 @@ class FhirR4Builder [['fullUrl' => 'urn:uuid:' . $compositionUuid, 'resource' => $composition]], $resourceEntries ), - ]; + ]); } /** @@ -1547,7 +1547,7 @@ class FhirR4Builder $composition['author'] = [['display' => $provider]]; } - return [ + return $this->sanitizeBundle([ 'resourceType' => 'Bundle', 'type' => 'document', 'timestamp' => $issuedAt, @@ -1567,7 +1567,7 @@ class FhirR4Builder ], [ 'resource' => $claimResource, ]], - ]; + ]); } /** @@ -1866,7 +1866,7 @@ class FhirR4Builder } $hfrId = trim((string) ($organization['hfr_id'] ?? $organization['id'] ?? '')); - return [ + return $this->sanitizeBundle([ 'resourceType' => 'Bundle', 'id' => $bundleUuid, 'meta' => [ @@ -1884,7 +1884,7 @@ class FhirR4Builder 'type' => 'document', 'timestamp' => $issuedAt, 'entry' => array_merge([['fullUrl' => 'urn:uuid:' . $compositionUuid, 'resource' => $composition]], $resourceEntries), - ]; + ]); } private function normalizeImmunizationStatus(string $status): string @@ -1977,6 +1977,38 @@ class FhirR4Builder return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); } + /** @param array $bundle + * @return array + */ + private function sanitizeBundle(array $bundle): array + { + return $this->sanitizeFhirValue($bundle); + } + + /** @param mixed $value + * @return mixed + */ + private function sanitizeFhirValue($value) + { + if (is_string($value)) { + return in_array(strtoupper(trim($value)), ['NA', 'N/A', 'NULL', 'NOT AVAILABLE'], true) ? null : $value; + } + if (! is_array($value)) { + return $value; + } + + $clean = []; + foreach ($value as $key => $nested) { + $sanitized = $this->sanitizeFhirValue($nested); + if ($sanitized === null || $sanitized === '' || $sanitized === []) { + continue; + } + $clean[$key] = $sanitized; + } + + return $clean; + } + /** ISO 8601 timestamp with India Standard Time offset (+05:30). */ private function isoTimestamp(): string { diff --git a/app/Views/billing/Patient_V.php b/app/Views/billing/Patient_V.php index 94f6218..161adbc 100644 --- a/app/Views/billing/Patient_V.php +++ b/app/Views/billing/Patient_V.php @@ -441,7 +441,7 @@
- +
@@ -759,12 +759,17 @@
Facility: —
HFR ID: —
-
Source: ABDM Bridge GET /api/v3/facility/qr-code.
+
+ + After the patient scans, confirm the incoming token here. +
- +
@@ -1341,7 +1346,7 @@ address: { icon: '@', title: 'By ABHA Address', sub: 'Secure modal verification' }, qr: { icon: '📷', title: 'Scan Patient ABHA QR', sub: 'Scan QR → patient details' }, facility: { icon: '🏥', title: 'Scan Facility QR', sub: 'Patient scans via ABHA app' }, - mobile: { icon: '📱', title: 'By Mobile OTP', sub: 'OTP to ABHA-linked mobile' } + mobile: { icon: '📱', title: 'Find ABHA via Mobile', sub: 'Find an ABHA linked to this mobile number' } }; window.abhaRegSelectMethod = function(method) { diff --git a/app/Views/partials/abha_create_panel.php b/app/Views/partials/abha_create_panel.php index d4933e5..ff7f858 100644 --- a/app/Views/partials/abha_create_panel.php +++ b/app/Views/partials/abha_create_panel.php @@ -271,7 +271,7 @@ $_aadh = ''; - +
@@ -283,7 +283,7 @@ $_aadh = '';