diff --git a/app/Controllers/Abha.php b/app/Controllers/Abha.php index 7aa05f3..5c3e091 100644 --- a/app/Controllers/Abha.php +++ b/app/Controllers/Abha.php @@ -505,6 +505,45 @@ class Abha extends BaseController ]); } + // ------------------------------------------------------------------------- + // Fetch official ABHA card — POST abha/register/card + // ------------------------------------------------------------------------- + public function fetchOfficialCard() + { + if (! $this->request->isAJAX()) { + return $this->response->setStatusCode(400)->setJSON(['ok' => 0, 'error_text' => 'Invalid request']); + } + + $abhaNumber = trim((string) ($this->request->getPost('abha_number') ?? '')); + $abhaAddress = trim((string) ($this->request->getPost('abha_address') ?? '')); + + if ($abhaNumber === '' && $abhaAddress === '') { + return $this->response->setJSON(['ok' => 0, 'error_text' => 'ABHA number or ABHA address is required']); + } + + try { + $result = AbdmConnectorFactory::make()->fetchOfficialAbhaCard([ + 'abha_number' => $abhaNumber, + 'abha_address' => $abhaAddress, + ]); + } catch (\Throwable $e) { + log_message('error', '[ABHA] Official card fetch failed: ' . $e->getMessage()); + return $this->response->setStatusCode(502)->setJSON([ + 'ok' => 0, + 'error_text' => 'The official ABHA card could not be downloaded from the Bridge.', + ]); + } + + if (empty($result['ok']) || (int) $result['ok'] !== 1) { + return $this->response->setStatusCode(502)->setJSON([ + 'ok' => 0, + 'error_text' => $this->extractBridgeErrorText($result, 'The official ABHA card was not returned by the Bridge.'), + ]); + } + + return $this->response->setJSON($result); + } + // ------------------------------------------------------------------------- // ABHA Card view — GET abha/card/{abha_number} // Renders a printable card page for a patient whose ABHA is stored in HMS. @@ -1671,6 +1710,7 @@ class Abha extends BaseController $candidates = [ $payload['ABHAProfile'] ?? null, + $payload['data']['ABHAProfile'] ?? null, $payload['gateway_abha_profile'] ?? null, $payload['data']['gateway_abha_profile'] ?? null, $payload['gateway_patient'] ?? null, @@ -1750,12 +1790,18 @@ class Abha extends BaseController return $this->response->setStatusCode(400)->setJSON(['ok' => 0, 'error_text' => 'Invalid request']); } - $input = trim((string) ($this->request->getPost('abha_id') ?? $this->request->getPost('abha_address') ?? '')); + $lookupType = strtolower(trim((string) ($this->request->getPost('lookup_type') ?? ''))); + $postedNumber = trim((string) ($this->request->getPost('abha_id') ?? '')); + $postedAddress = trim((string) ($this->request->getPost('abha_address') ?? '')); + if (! in_array($lookupType, ['number', 'address'], true)) { + $lookupType = $postedAddress !== '' || str_contains($postedNumber, '@') ? 'address' : 'number'; + } + $input = $lookupType === 'address' ? $postedAddress : $postedNumber; if ($input === '') { - return $this->response->setJSON(['ok' => 0, 'error_text' => 'ABHA ID or address required']); + return $this->response->setJSON(['ok' => 0, 'error_text' => $lookupType === 'address' ? 'ABHA Address is required' : 'ABHA Number or ID is required']); } - $isAddress = str_contains($input, '@'); + $isAddress = $lookupType === 'address'; $payload = $isAddress ? ['abha_address' => $input] : ['abha_id' => preg_replace('/\D/', '', $input)]; @@ -2163,7 +2209,19 @@ class Abha extends BaseController private function extractAbhaCardData(array $payload): string { - $sources = [$payload, $payload['data'] ?? [], $payload['account'] ?? [], $payload['profile'] ?? [], $payload['gateway_patient'] ?? [], $payload['data']['gateway_patient'] ?? []]; + $data = is_array($payload['data'] ?? null) ? $payload['data'] : []; + $sources = [ + $payload, + $data, + $payload['account'] ?? [], + $data['account'] ?? [], + $payload['profile'] ?? [], + $data['profile'] ?? [], + $payload['ABHAProfile'] ?? [], + $data['ABHAProfile'] ?? [], + $payload['gateway_patient'] ?? [], + $data['gateway_patient'] ?? [], + ]; foreach ($sources as $source) { if (! is_array($source)) { continue; } foreach (['official_card', 'card_data_uri', 'card_base64', 'card_data', 'abhaCard', 'abha_card', 'cardData', 'card'] as $key) { diff --git a/app/Libraries/Abdm/EAtriaBridgeConnector.php b/app/Libraries/Abdm/EAtriaBridgeConnector.php index b1919a0..e133745 100644 --- a/app/Libraries/Abdm/EAtriaBridgeConnector.php +++ b/app/Libraries/Abdm/EAtriaBridgeConnector.php @@ -708,6 +708,46 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface return $this->attachOfficialAbhaCard($result); } + public function fetchOfficialAbhaCard(array $payload): array + { + $abhaNumber = trim((string) ($payload['abha_number'] ?? $payload['abhaNumber'] ?? '')); + $abhaAddress = trim((string) ($payload['abha_address'] ?? $payload['abhaAddress'] ?? '')); + + if ($abhaNumber === '' && $abhaAddress === '') { + return ['ok' => 0, 'error_text' => 'ABHA number or ABHA address is required']; + } + + $result = $this->get('/v3/abha/card', array_filter([ + 'abha_number' => $abhaNumber, + 'abha_address' => $abhaAddress, + ], static fn($value): bool => trim((string) $value) !== '')); + + if (empty($result['ok']) || (int) $result['ok'] !== 1) { + return $result; + } + + $data = is_array($result['data'] ?? null) ? $result['data'] : $result; + $card = $this->extractOfficialCard($result) ?: $this->extractOfficialCard($data); + $source = $this->extractCardSource($result) ?: $this->extractCardSource($data); + + if ($card === '' || $source !== 'abdm') { + return [ + 'ok' => 0, + 'error_text' => 'The Bridge did not return an official ABHA card.', + 'card_source' => $source, + 'request_id' => (string) ($result['request_id'] ?? ''), + ]; + } + + return [ + 'ok' => 1, + 'card_base64' => $card, + 'card_content_type' => $this->resolveCardContentType($data), + 'card_source' => $source, + 'request_id' => (string) ($result['request_id'] ?? ''), + ]; + } + /** * Ensure a verify/enrol response carries the official ABHA card, fetching it * from the Bridge card endpoint when the response itself omits it. @@ -730,7 +770,8 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface $card = $this->extractOfficialCard($result) ?: $this->extractOfficialCard($data) ?: $this->extractOfficialCard($account) - ?: $this->extractOfficialCard($profile); + ?: $this->extractOfficialCard($profile) + ?: $this->extractOfficialCard(is_array($result['ABHAProfile'] ?? null) ? $result['ABHAProfile'] : []); if ($card !== '') { $result['card_source'] = $this->extractCardSource($result) ?: $this->extractCardSource($data); diff --git a/app/Views/billing/Patient_V.php b/app/Views/billing/Patient_V.php index 0609ffd..4d0fa8e 100644 --- a/app/Views/billing/Patient_V.php +++ b/app/Views/billing/Patient_V.php @@ -439,8 +439,9 @@
  • View and download the ABHA card
  • - - + + +
    @@ -527,7 +528,6 @@ @@ -1337,7 +1337,8 @@ var csrf = function() { return $('input[name=""]').first().val(); }; var methodMeta = { - number: { icon: '🆔', title: 'By ABHA Number/Address', sub: 'Secure modal verification' }, + number: { icon: '🆔', title: 'By ABHA Number or ID', sub: 'Secure modal verification' }, + 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' } @@ -1357,7 +1358,10 @@ $('#abhareg_result').addClass('d-none'); if (method === 'number') { $('#abhareg_panel_number').addClass('d-none'); - openAbhaAccountVerification('', null); + openAbhaAccountVerification('', null, 'number'); + } else if (method === 'address') { + $('#abhareg_panel_number').addClass('d-none'); + openAbhaAccountVerification('', null, 'address'); } else if (method === 'mobile') { $('#abhareg_panel_mobile').addClass('d-none'); openAbhaMobileVerification(); @@ -1393,7 +1397,7 @@ }); } - function openAbhaAccountVerification(identifier, lookupResponse) { + function openAbhaAccountVerification(identifier, lookupResponse, lookupType) { if (!window.AbhaVerifyModal) { regAlert('abhareg_num_stepA_alert', 'danger', 'ABHA verification modal is unavailable. Reload the page and try again.'); return; @@ -1406,7 +1410,7 @@ window.AbhaPatientMatchModal.open(profile, profile.candidates || [], function(finalResponse) { showRegResult(finalResponse); }); - }); + }, lookupType || 'number'); } $(document).off('click.abhaLauncher').on('click.abhaLauncher', '#abha_open_create_modal_btn', function() { diff --git a/app/Views/partials/abha_verify_modal.php b/app/Views/partials/abha_verify_modal.php index 929dcb5..f878ba4 100644 --- a/app/Views/partials/abha_verify_modal.php +++ b/app/Views/partials/abha_verify_modal.php @@ -37,13 +37,13 @@
    - +
    - +
    -
    Enter the patient's ABHA identity. A mobile number is not required for account lookup.
    +
    Enter the patient's 14-digit ABHA Number or ID. A mobile number is not required for account lookup.
    @@ -106,6 +106,7 @@ window.AbhaVerifyModal = (function () { var lookupResponse = null; var verifiedProfile = null; var onVerified = null; + var lookupType = 'number'; var resendTimer = null; var resendRemaining = 0; @@ -169,9 +170,11 @@ window.AbhaVerifyModal = (function () { } function lookup() { var identifier = $('#abhaVerifyIdentifier').val().trim(); - if (!identifier) { alertBox('warning', 'Enter an ABHA number or ABHA address.'); return; } + if (!identifier) { alertBox('warning', lookupType === 'address' ? 'Enter an ABHA Address.' : 'Enter an ABHA Number or ID.'); return; } var button = $('#abhaVerifyLookupBtn').prop('disabled', true).html('Looking up'); - $.post('', { abha_id:identifier, '':csrf() }, function (response) { + var request = { lookup_type: lookupType, '':csrf() }; + request[lookupType === 'address' ? 'abha_address' : 'abha_id'] = identifier; + $.post('', request, function (response) { button.prop('disabled', false).html('Find Account'); if (!response || response.ok != 1 || response.status !== 'VALID') { alertBox('danger', apiMessage(response, 'ABHA account was not found.')); return; } if (!response.txn_id) { alertBox('warning', 'The Bridge found this ABHA but did not return the login transaction ID. Update /v3/abha/login/search before OTP verification can continue.'); return; } @@ -276,10 +279,16 @@ window.AbhaVerifyModal = (function () { $('#abhaVerifyModal').on('hidden.bs.modal', stopTimer); }); return { - open: function (identifier, initialLookup, callback) { + open: function (identifier, initialLookup, callback, requestedLookupType) { lookupResponse = initialLookup || null; verifiedProfile = null; onVerified = callback; + lookupType = requestedLookupType === 'address' ? 'address' : 'number'; + $('#abhaVerifyIdentifierLabel').text(lookupType === 'address' ? 'ABHA Address' : 'ABHA Number or ID'); + $('#abhaVerifyIdentifier').attr('placeholder', lookupType === 'address' ? 'username@abdm' : '91-0000-0000-0000'); + $('#abhaVerifyIdentifierHelp').text(lookupType === 'address' + ? 'Enter the patient\'s ABHA Address, for example username@abdm.' + : 'Enter the patient\'s 14-digit ABHA Number or ID. A mobile number is not required for account lookup.'); $('#abhaVerifyIdentifier').val(identifier || ''); $('#abhaVerifyPhoto,#abhaVerifyDownloadCard').addClass('d-none'); showStep(1);