From 6a6895ce35adaf36678f402e1c5113f6962b7da7 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Thu, 13 Aug 2026 19:51:14 +0530 Subject: [PATCH] Add ABHA QR scan registration flow --- app/Config/Routes.php | 1 + app/Controllers/Abha.php | 98 +++++++++++++ app/Views/billing/Patient_V.php | 46 ++++++- app/Views/partials/abha_qr_modal.php | 198 +++++++++++++++++++++++++++ 4 files changed, 336 insertions(+), 7 deletions(-) create mode 100644 app/Views/partials/abha_qr_modal.php diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 7b23027..e92a6ca 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -776,6 +776,7 @@ $routes->post('abha/create/address', 'Abha::address', ['filter' => 'permission:a $routes->post('abha/create/address_suggestions', 'Abha::addressSuggestions', ['filter' => 'permission:abdm.abha.create']); $routes->get('abha/card/(:segment)', 'Abha::card/$1', ['filter' => 'permission:abdm.abha.create']); $routes->post('abha/register/validate', 'Abha::validateAbha', ['filter' => 'permission:abdm.abha.create']); +$routes->post('abha/register/qr_scan', 'Abha::qrScan', ['filter' => 'permission:abdm.abha.create']); $routes->post('abha/register/login/request-otp', 'Abha::loginRequestOtp', ['filter' => 'permission:abdm.abha.create']); $routes->post('abha/register/login/verify-otp', 'Abha::loginVerifyOtp', ['filter' => 'permission:abdm.abha.create']); $routes->get('abha/register/facility_qr', 'Abha::facilityQr', ['filter' => 'permission:abdm.abha.create']); diff --git a/app/Controllers/Abha.php b/app/Controllers/Abha.php index bf89658..2769738 100644 --- a/app/Controllers/Abha.php +++ b/app/Controllers/Abha.php @@ -2047,6 +2047,104 @@ class Abha extends BaseController ]); } + // ------------------------------------------------------------------------- + // ABHA QR scan — POST abha/register/qr_scan + // Accepts the JSON payload printed on an ABHA card / PHR app QR code and + // returns the demographics plus possible existing HMS patients. The QR is a + // demographic capture, not an OTP authentication, so it is never marked VERIFIED. + // ------------------------------------------------------------------------- + public function qrScan() + { + if (! $this->request->isAJAX()) { + return $this->response->setStatusCode(400)->setJSON(['ok' => 0, 'error_text' => 'Invalid request']); + } + + $raw = trim((string) ($this->request->getPost('qr_data') ?? '')); + if ($raw === '') { + return $this->response->setJSON(['ok' => 0, 'error_text' => 'Scan or paste the ABHA QR content first.']); + } + + $decoded = json_decode($raw, true); + if (! is_array($decoded)) { + // Some cards/apps encode only the ABHA number or address. + $digits = preg_replace('/\D/', '', $raw); + if (strlen($digits) === 14) { + $decoded = ['hidn' => $digits]; + } elseif (str_contains($raw, '@')) { + $decoded = ['hid' => $raw]; + } else { + return $this->response->setJSON([ + 'ok' => 0, + 'error_text' => 'This QR code is not a recognised ABHA card. Scan the QR printed on the ABHA card or shown in the ABHA/PHR app.', + ]); + } + } + + // ABHA QR keys vary by issuer ("state name", "stateName", "state_name"). + $normalized = []; + foreach ($decoded as $key => $value) { + $flatKey = strtolower(preg_replace('/[^a-z0-9]/i', '', (string) $key)); + if ($flatKey !== '' && is_scalar($value)) { + $normalized[$flatKey] = trim((string) $value); + } + } + + $pick = static function (array $keys) use ($normalized): string { + foreach ($keys as $key) { + if (trim((string) ($normalized[$key] ?? '')) !== '') { + return trim((string) $normalized[$key]); + } + } + return ''; + }; + + $abhaNumber = preg_replace('/\D/', '', $pick(['hidn', 'abhanumber', 'healthidnumber', 'abhano', 'abhaid'])); + $abhaAddress = $pick(['hid', 'phr', 'phraddress', 'abhaaddress', 'healthid', 'preferredabhaaddress']); + $name = $pick(['name', 'fullname', 'patientname']); + $gender = $pick(['gender', 'sex']); + $dob = $pick(['dob', 'dateofbirth', 'birthdate']); + $mobile = preg_replace('/\D/', '', $pick(['mobile', 'mobilenumber', 'phone', 'contact'])); + $address = $pick(['address', 'addressline']); + $district = $pick(['districtname', 'district', 'distname']); + $state = $pick(['statename', 'state']); + $zip = $pick(['pincode', 'pin', 'postalcode', 'zip']); + + if ($abhaNumber === '' && $abhaAddress === '') { + return $this->response->setJSON([ + 'ok' => 0, + 'error_text' => 'The QR code did not contain an ABHA number or ABHA address.', + ]); + } + + if ($abhaNumber === '' && str_contains($abhaAddress, '@')) { + $candidateDigits = preg_replace('/\D/', '', explode('@', $abhaAddress)[0]); + if (strlen($candidateDigits) === 14) { + $abhaNumber = $candidateDigits; + } + } + + $db = \Config\Database::connect(); + $fields = $db->getFieldNames('patient_master') ?? []; + $abhaField = $this->resolveAbhaFieldName($fields); + + return $this->response->setJSON([ + 'ok' => 1, + 'need_confirmation' => true, + 'abha_number' => $abhaNumber, + 'abha_address' => $abhaAddress, + 'name' => $name, + 'mobile' => $mobile, + 'gender' => $gender, + 'dob' => $dob, + 'address' => $address, + 'district' => $district, + 'state' => $state, + 'zip' => $zip, + 'verification_type' => 'QR_SCAN', + 'candidates' => $this->findMatchingCandidates($db, $fields, $name, $mobile, $gender, $dob, '', $abhaField, $abhaNumber), + ]); + } + private function extractAbhaCardData(array $payload): string { $sources = [$payload, $payload['data'] ?? [], $payload['account'] ?? [], $payload['profile'] ?? [], $payload['gateway_patient'] ?? [], $payload['data']['gateway_patient'] ?? []]; diff --git a/app/Views/billing/Patient_V.php b/app/Views/billing/Patient_V.php index f71c7be..80022d5 100644 --- a/app/Views/billing/Patient_V.php +++ b/app/Views/billing/Patient_V.php @@ -43,6 +43,7 @@ +
@@ -731,7 +732,16 @@
-

Point camera at patient's ABHA QR code (from PHR app or printed card).

+

Scan the ABHA QR from the patient's card or PHR app.

+
+ + +
+
@@ -742,6 +752,7 @@
+
+
+ +
+
+
+ +