fix(abdm): resolve ABHA address verification, field error extraction, and task board sync
- Fix empty string fallback in validateAbha and EAtriaBridgeConnector to properly extract healthIdNumber and real fullName from data payload - Skip generic bridge envelope messages in extractBridgeErrorText to surface specific field rejections like loginHint - Translate non-existent ABHA address errors to 'ABHA Address does not exist or was not found in the ABDM registry' - Support both 14-digit ABHA numbers and ABHA addresses in Diagnosis publish and ABDM Task Board - Prevent completed task duplication in lab/radiology backfill - Add unit tests in AbhaOtpErrorHandlingTest and AbdmTaskBoardBackfillTest
This commit is contained in:
parent
8dd34a9085
commit
fd10df43c3
|
|
@ -204,7 +204,11 @@ class AbdmTaskBoard extends BaseController
|
|||
|
||||
$abhaId = trim((string) $this->request->getPost('abha_id'));
|
||||
if (! $this->isValidAbhaNumber($abhaId)) {
|
||||
return $this->response->setJSON(['ok' => 0, 'error_text' => 'ABHA ID must be a 14-digit number']);
|
||||
return $this->response->setJSON(['ok' => 0, 'error_text' => 'ABHA ID must be a 14-digit number or valid ABHA address']);
|
||||
}
|
||||
$digits = preg_replace('/\D/', '', $abhaId);
|
||||
if (strlen($digits) === 14) {
|
||||
$abhaId = $digits;
|
||||
}
|
||||
|
||||
$taskType = (string) ($task['task_type'] ?? '');
|
||||
|
|
@ -328,7 +332,12 @@ class AbdmTaskBoard extends BaseController
|
|||
|
||||
private function isValidAbhaNumber(string $abhaId): bool
|
||||
{
|
||||
return preg_match('/^\d{14}$/', $abhaId) === 1;
|
||||
$clean = preg_replace('/\D/', '', $abhaId);
|
||||
if (strlen($clean) === 14) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return str_contains($abhaId, '@') && preg_match('/^[a-zA-Z0-9.\-_]{3,}@[a-zA-Z]{3,}$/', $abhaId) === 1;
|
||||
}
|
||||
|
||||
private function backfillPatientAbhaTasks(): void
|
||||
|
|
@ -356,7 +365,7 @@ class AbdmTaskBoard extends BaseController
|
|||
}
|
||||
|
||||
$abha = trim((string) ($row[$abhaField] ?? ''));
|
||||
if (preg_match('/^\d{14}$/', $abha) === 1) {
|
||||
if ($this->isValidAbhaNumber($abha)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -446,8 +455,25 @@ class AbdmTaskBoard extends BaseController
|
|||
continue;
|
||||
}
|
||||
|
||||
$abha = trim((string) ($row['abha_id'] ?? $row['abha_no'] ?? $row['abha_address'] ?? $row['abha'] ?? ''));
|
||||
if (preg_match('/^\d{14}$/', $abha) !== 1) {
|
||||
$rawAbha = '';
|
||||
$abhaAddress = '';
|
||||
foreach (['abha_address', 'abha_id', 'abha_no', 'abha'] as $f) {
|
||||
$val = trim((string) ($row[$f] ?? ''));
|
||||
if ($val === '') {
|
||||
continue;
|
||||
}
|
||||
if ($abhaAddress === '' && str_contains($val, '@')) {
|
||||
$abhaAddress = $val;
|
||||
continue;
|
||||
}
|
||||
$digits = preg_replace('/\D/', '', $val);
|
||||
if ($rawAbha === '' && strlen($digits) === 14) {
|
||||
$rawAbha = $digits;
|
||||
}
|
||||
}
|
||||
|
||||
$abha = $rawAbha !== '' ? $rawAbha : $abhaAddress;
|
||||
if ($abha === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -477,6 +503,7 @@ class AbdmTaskBoard extends BaseController
|
|||
->where('task_type', $taskType)
|
||||
->where('entity_type', 'lab_request')
|
||||
->where('entity_id', (string) $labReqId)
|
||||
->whereIn('status', ['pending', 'in_progress', 'completed'])
|
||||
->get(1)
|
||||
->getRowArray();
|
||||
if (! empty($exists)) {
|
||||
|
|
|
|||
|
|
@ -1698,9 +1698,22 @@ class Abha extends BaseController
|
|||
$result['data']['description'] ?? null,
|
||||
];
|
||||
|
||||
$genericPhrases = [
|
||||
'abdm gateway returned an error.',
|
||||
'abdm_upstream_error',
|
||||
'gateway error',
|
||||
'upstream error',
|
||||
'bad request',
|
||||
'an error occurred.',
|
||||
];
|
||||
$firstGeneric = null;
|
||||
|
||||
foreach ($candidates as $candidate) {
|
||||
if (is_string($candidate) && trim($candidate) !== '') {
|
||||
$trimmed = trim($candidate);
|
||||
if (stripos($trimmed, 'loginHint') !== false && stripos($trimmed, 'invalid') !== false) {
|
||||
return 'ABDM gateway rejected login hint: OTP verification by ABHA Address is not supported directly by ABDM. Please verify using the 14-digit ABHA Number or Find ABHA via Mobile.';
|
||||
}
|
||||
if (stripos($trimmed, 'loginId') !== false && stripos($trimmed, 'invalid') !== false) {
|
||||
return 'Aadhaar Number is not valid. Valid 12-digit Aadhaar number is required.';
|
||||
}
|
||||
|
|
@ -1719,6 +1732,10 @@ class Abha extends BaseController
|
|||
}
|
||||
return 'Incorrect OTP';
|
||||
}
|
||||
if (in_array(strtolower($cleaned), $genericPhrases, true)) {
|
||||
$firstGeneric ??= $cleaned;
|
||||
continue;
|
||||
}
|
||||
return $cleaned;
|
||||
}
|
||||
}
|
||||
|
|
@ -1739,7 +1756,9 @@ class Abha extends BaseController
|
|||
continue;
|
||||
}
|
||||
$fieldMsg = trim($message);
|
||||
if (strcasecmp($field, 'loginId') === 0 && stripos($fieldMsg, 'invalid') !== false) {
|
||||
if (strcasecmp($field, 'loginHint') === 0 && stripos($fieldMsg, 'invalid') !== false) {
|
||||
$fieldErrors[] = 'ABDM gateway rejected login hint: OTP verification by ABHA Address is not supported directly by ABDM. Please verify using the 14-digit ABHA Number or Find ABHA via Mobile.';
|
||||
} elseif (strcasecmp($field, 'loginId') === 0 && stripos($fieldMsg, 'invalid') !== false) {
|
||||
$fieldErrors[] = 'Aadhaar Number is not valid. Valid 12-digit Aadhaar number is required.';
|
||||
} elseif (preg_match('/(?:attempts.*exceeded|403)/i', $fieldMsg)) {
|
||||
$fieldErrors[] = 'Incorrect OTP. Maximum number of attempts exceeded. Please generate a fresh OTP and try again.';
|
||||
|
|
@ -1754,6 +1773,10 @@ class Abha extends BaseController
|
|||
return implode('; ', $fieldErrors);
|
||||
}
|
||||
|
||||
if ($firstGeneric !== null) {
|
||||
return $firstGeneric;
|
||||
}
|
||||
|
||||
if (preg_match('/(?:invalid.*otp|incorrect.*otp|otp.*expired)/i', $fallback)) {
|
||||
return 'Incorrect OTP';
|
||||
}
|
||||
|
|
@ -2834,7 +2857,9 @@ class Abha extends BaseController
|
|||
$errorText .= ' (Ref: ' . $requestId . ')';
|
||||
}
|
||||
|
||||
if (stripos($errorText, 'Please make a valid request.') !== false) {
|
||||
if (stripos($errorText, 'Unknown column') !== false || stripos($errorText, 'preferredAbhaAddress') !== false || stripos($errorText, 'not found') !== false || stripos($errorText, 'does not exist') !== false) {
|
||||
$errorText = 'ABHA Address does not exist or was not found in the ABDM registry.';
|
||||
} elseif (stripos($errorText, 'Please make a valid request.') !== false) {
|
||||
$errorText = 'Bridge validation rejected this request. Please contact bridge support with the reference shown. '
|
||||
. $errorText;
|
||||
}
|
||||
|
|
@ -2915,13 +2940,81 @@ class Abha extends BaseController
|
|||
'blocked_auth_methods' => is_array($blockedMethods) ? array_values($blockedMethods) : [],
|
||||
'masked_mobile' => $maskedMobile,
|
||||
'aadhaar_masked_mobile' => $aadhaarMaskedMobile,
|
||||
'account' => [
|
||||
'name' => trim((string) ($account['name'] ?? $account['fullName'] ?? $account['full_name'] ?? $data['name'] ?? $data['fullName'] ?? $data['full_name'] ?? '')),
|
||||
'abha_number' => trim((string) ($account['ABHANumber'] ?? $account['abhaNumber'] ?? $account['abha_id'] ?? $account['healthIdNumber'] ?? $data['ABHANumber'] ?? $data['healthIdNumber'] ?? $data['abha_id'] ?? '')),
|
||||
'abha_address' => trim((string) ($account['abhaAddress'] ?? $account['preferredAddress'] ?? $account['preferredAbhaAddress'] ?? $account['abha_address'] ?? $data['abhaAddress'] ?? $data['preferredAddress'] ?? $data['preferredAbhaAddress'] ?? $data['abha_address'] ?? '')),
|
||||
'masked_mobile' => $maskedMobile,
|
||||
'aadhaar_masked_mobile' => $aadhaarMaskedMobile,
|
||||
],
|
||||
'account' => (function () use ($account, $data, $result, $maskedMobile, $aadhaarMaskedMobile): array {
|
||||
$resolvedAbhaNumber = '';
|
||||
foreach ([
|
||||
$data['healthIdNumber'] ?? null,
|
||||
$data['ABHANumber'] ?? null,
|
||||
$data['abhaNumber'] ?? null,
|
||||
$data['abha_number'] ?? null,
|
||||
$data['abha_id'] ?? null,
|
||||
$account['healthIdNumber'] ?? null,
|
||||
$account['ABHANumber'] ?? null,
|
||||
$account['abhaNumber'] ?? null,
|
||||
$account['abha_number'] ?? null,
|
||||
$account['abha_id'] ?? null,
|
||||
$result['healthIdNumber'] ?? null,
|
||||
$result['ABHANumber'] ?? null,
|
||||
$result['abhaNumber'] ?? null,
|
||||
$result['abha_number'] ?? null,
|
||||
$result['abha_id'] ?? null,
|
||||
] as $candidateNum) {
|
||||
if (is_string($candidateNum) && trim($candidateNum) !== '') {
|
||||
$resolvedAbhaNumber = trim($candidateNum);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$resolvedName = '';
|
||||
foreach ([
|
||||
$data['fullName'] ?? null,
|
||||
$data['full_name'] ?? null,
|
||||
$data['name'] ?? null,
|
||||
$account['fullName'] ?? null,
|
||||
$account['full_name'] ?? null,
|
||||
$account['name'] ?? null,
|
||||
$result['fullName'] ?? null,
|
||||
$result['full_name'] ?? null,
|
||||
$result['name'] ?? null,
|
||||
] as $candidateName) {
|
||||
if (is_string($candidateName) && trim($candidateName) !== '' && strcasecmp(trim($candidateName), 'ABHA User') !== 0) {
|
||||
$resolvedName = trim($candidateName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($resolvedName === '') {
|
||||
$resolvedName = trim((string) ($account['name'] ?? $data['name'] ?? $result['name'] ?? 'ABHA User'));
|
||||
}
|
||||
|
||||
$resolvedAddress = '';
|
||||
foreach ([
|
||||
$data['abhaAddress'] ?? null,
|
||||
$data['preferredAddress'] ?? null,
|
||||
$data['preferredAbhaAddress'] ?? null,
|
||||
$data['abha_address'] ?? null,
|
||||
$account['abhaAddress'] ?? null,
|
||||
$account['preferredAddress'] ?? null,
|
||||
$account['preferredAbhaAddress'] ?? null,
|
||||
$account['abha_address'] ?? null,
|
||||
$result['abhaAddress'] ?? null,
|
||||
$result['preferredAddress'] ?? null,
|
||||
$result['preferredAbhaAddress'] ?? null,
|
||||
$result['abha_address'] ?? null,
|
||||
] as $candidateAddr) {
|
||||
if (is_string($candidateAddr) && trim($candidateAddr) !== '') {
|
||||
$resolvedAddress = trim($candidateAddr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => $resolvedName,
|
||||
'abha_number' => $resolvedAbhaNumber,
|
||||
'abha_address' => $resolvedAddress,
|
||||
'masked_mobile' => $maskedMobile,
|
||||
'aadhaar_masked_mobile' => $aadhaarMaskedMobile,
|
||||
];
|
||||
})(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4549,7 +4549,23 @@ class Diagnosis extends BaseController
|
|||
}
|
||||
|
||||
if ($patient) {
|
||||
$abhaId = trim((string) ($patient->abha_id ?? $patient->abha_no ?? $patient->abha_address ?? $patient->abha ?? ''));
|
||||
$rawAbha = '';
|
||||
$abhaAddress = '';
|
||||
foreach (['abha_address', 'abha_id', 'abha_no', 'abha'] as $f) {
|
||||
$val = trim((string) ($patient->{$f} ?? ''));
|
||||
if ($val === '') {
|
||||
continue;
|
||||
}
|
||||
if ($abhaAddress === '' && str_contains($val, '@')) {
|
||||
$abhaAddress = $val;
|
||||
continue;
|
||||
}
|
||||
$digits = preg_replace('/\D/', '', $val);
|
||||
if ($rawAbha === '' && strlen($digits) === 14) {
|
||||
$rawAbha = $digits;
|
||||
}
|
||||
}
|
||||
$abhaId = $rawAbha !== '' ? $rawAbha : $abhaAddress;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4568,8 +4584,15 @@ class Diagnosis extends BaseController
|
|||
$bridgeSync = new BridgeSyncService();
|
||||
$bridgeSync->enqueue($eventType, $payload, 'lab_request', (string) $labReqId);
|
||||
|
||||
$cleanDigits = preg_replace('/\D/', '', $abhaId);
|
||||
$hasValidAbha = ($cleanDigits !== '' && strlen($cleanDigits) === 14)
|
||||
|| (str_contains($abhaId, '@') && preg_match('/^[a-zA-Z0-9.\-_]{3,}@[a-zA-Z]{3,}$/', $abhaId) === 1);
|
||||
|
||||
// Create ABDM work tasks for verified/finalized reports so staff can submit them.
|
||||
if ((int) ($labReq->status ?? 0) >= 2 && preg_match('/^\d{14}$/', $abhaId) === 1) {
|
||||
if ((int) ($labReq->status ?? 0) >= 2 && $hasValidAbha) {
|
||||
if (strlen($cleanDigits) === 14) {
|
||||
$abhaId = $cleanDigits;
|
||||
}
|
||||
$taskType = $this->isRadiologyType((int) ($labReq->lab_type ?? 0))
|
||||
? 'radiology_report_publish'
|
||||
: 'lab_report_publish';
|
||||
|
|
|
|||
|
|
@ -696,6 +696,36 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
$result['aadhaar_masked_mobile'] = $aadhaarMaskedMobile;
|
||||
}
|
||||
|
||||
$resolvedAbhaNumber = '';
|
||||
foreach ([
|
||||
$data['healthIdNumber'] ?? null,
|
||||
$data['ABHANumber'] ?? null,
|
||||
$data['abhaNumber'] ?? null,
|
||||
$data['abha_number'] ?? null,
|
||||
$firstAccount['healthIdNumber'] ?? null,
|
||||
$firstAccount['ABHANumber'] ?? null,
|
||||
$firstAccount['abhaNumber'] ?? null,
|
||||
$firstAccount['abha_number'] ?? null,
|
||||
$result['abha_number'] ?? null,
|
||||
$result['healthIdNumber'] ?? null,
|
||||
] as $candidateNum) {
|
||||
if (is_string($candidateNum) && trim($candidateNum) !== '') {
|
||||
$resolvedAbhaNumber = trim($candidateNum);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($resolvedAbhaNumber !== '') {
|
||||
$result['abha_number'] = $resolvedAbhaNumber;
|
||||
if (isset($result['account']) && is_array($result['account'])) {
|
||||
$result['account']['abha_number'] = $resolvedAbhaNumber;
|
||||
$result['account']['abhaNumber'] = $resolvedAbhaNumber;
|
||||
}
|
||||
if (isset($result['data']['account']) && is_array($result['data']['account'])) {
|
||||
$result['data']['account']['abha_number'] = $resolvedAbhaNumber;
|
||||
$result['data']['account']['abhaNumber'] = $resolvedAbhaNumber;
|
||||
}
|
||||
}
|
||||
|
||||
// HMS callers expect a status-like field; treat successful search as VALID when absent.
|
||||
$result['status'] = $status !== '' ? $status : 'VALID';
|
||||
return $result;
|
||||
|
|
|
|||
|
|
@ -1202,11 +1202,18 @@ class AbdmTaskBoardSyncService
|
|||
$dateFields = array_intersect(['reported_time', 'collected_time', 'Request_Date'], $labFields);
|
||||
$dateSelect = ! empty($dateFields) ? (', r.' . implode(', r.', $dateFields)) : '';
|
||||
|
||||
$abhaParts = [];
|
||||
foreach (['abha_id', 'abha_no', 'abha_address', 'abha'] as $f) {
|
||||
if (in_array($f, $patientFields, true)) {
|
||||
$abhaParts[] = 'p.' . $f;
|
||||
}
|
||||
}
|
||||
$abhaSelect = ! empty($abhaParts) ? (', ' . implode(', ', $abhaParts)) : '';
|
||||
|
||||
$labRows = $this->db->table('lab_request r')
|
||||
->select('r.id, r.patient_id, r.patient_name, r.lab_type, r.charge_id, r.status' . $dateSelect . ', p.' . $abhaCol . ' as abha_id', false)
|
||||
->select('r.id, r.patient_id, r.patient_name, r.lab_type, r.charge_id, r.status' . $dateSelect . $abhaSelect, false)
|
||||
->join('patient_master p', 'p.id = r.patient_id', 'inner')
|
||||
->where('r.status >=', 2)
|
||||
->where('p.' . $abhaCol . ' !=', '')
|
||||
->orderBy('r.id', 'DESC')
|
||||
->limit($limit)
|
||||
->get()
|
||||
|
|
@ -1215,8 +1222,29 @@ class AbdmTaskBoardSyncService
|
|||
foreach ($labRows as $lRow) {
|
||||
$labReqId = (int) ($lRow['id'] ?? 0);
|
||||
$patientId = (int) ($lRow['patient_id'] ?? 0);
|
||||
$abhaId = trim((string) ($lRow['abha_id'] ?? ''));
|
||||
if ($labReqId <= 0 || $patientId <= 0 || preg_match('/^\d{14}$/', $abhaId) !== 1) {
|
||||
if ($labReqId <= 0 || $patientId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rawAbha = '';
|
||||
$abhaAddress = '';
|
||||
foreach (['abha_address', 'abha_id', 'abha_no', 'abha'] as $f) {
|
||||
$val = trim((string) ($lRow[$f] ?? ''));
|
||||
if ($val === '') {
|
||||
continue;
|
||||
}
|
||||
if ($abhaAddress === '' && str_contains($val, '@')) {
|
||||
$abhaAddress = $val;
|
||||
continue;
|
||||
}
|
||||
$digits = preg_replace('/\D/', '', $val);
|
||||
if ($rawAbha === '' && strlen($digits) === 14) {
|
||||
$rawAbha = $digits;
|
||||
}
|
||||
}
|
||||
|
||||
$abhaId = $rawAbha !== '' ? $rawAbha : $abhaAddress;
|
||||
if ($abhaId === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1081,9 +1081,13 @@
|
|||
var taskType = (row.getAttribute('data-task-type') || '').toLowerCase();
|
||||
var abhaInput = row.querySelector('.abha-input');
|
||||
var abhaId = (abhaInput ? abhaInput.value : (row.getAttribute('data-abha-id') || '')).trim();
|
||||
var abhaClean = abhaId.replace(/\D/g, '');
|
||||
if (abhaClean.length === 14) {
|
||||
abhaId = abhaClean;
|
||||
}
|
||||
|
||||
if (!/^\d{14}$/.test(abhaId)) {
|
||||
setStatus('ABHA must be a 14-digit number before FHIR preview.', true);
|
||||
if (abhaClean.length !== 14 && (!abhaId.includes('@') || !/^[a-zA-Z0-9.\-_]{3,}@[a-zA-Z]{3,}$/.test(abhaId))) {
|
||||
setStatus('ABHA must be a 14-digit number or valid ABHA address before FHIR preview.', true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1220,8 +1224,13 @@
|
|||
var recordId = row ? (parseInt(row.getAttribute('data-entity-id') || '0', 10) || 0) : 0;
|
||||
var taskId = row ? (parseInt(row.getAttribute('data-task-id') || '0', 10) || 0) : 0;
|
||||
|
||||
if (!/^\d{14}$/.test(abhaId) || patientId <= 0 || recordId <= 0) {
|
||||
setStatus('A 14-digit ABHA and valid Immunization record are required.', true);
|
||||
var abhaClean = abhaId.replace(/\D/g, '');
|
||||
if (abhaClean.length === 14) {
|
||||
abhaId = abhaClean;
|
||||
}
|
||||
var hasValidAbha = abhaClean.length === 14 || (abhaId.includes('@') && /^[a-zA-Z0-9.\-_]{3,}@[a-zA-Z]{3,}$/.test(abhaId));
|
||||
if (!hasValidAbha || patientId <= 0 || recordId <= 0) {
|
||||
setStatus('A 14-digit ABHA or valid ABHA address and valid Immunization record are required.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1308,8 +1317,13 @@
|
|||
return;
|
||||
}
|
||||
var abha = modalAbhaInput.value.trim();
|
||||
if (!/^\d{14}$/.test(abha)) {
|
||||
setStatus('ABHA must be a 14-digit number.', true);
|
||||
var abhaClean = abha.replace(/\D/g, '');
|
||||
if (abhaClean.length === 14) {
|
||||
abha = abhaClean;
|
||||
}
|
||||
var hasValidAbha = abhaClean.length === 14 || (abha.includes('@') && /^[a-zA-Z0-9.\-_]{3,}@[a-zA-Z]{3,}$/.test(abha));
|
||||
if (!hasValidAbha) {
|
||||
setStatus('ABHA must be a 14-digit number or valid ABHA address.', true);
|
||||
return;
|
||||
}
|
||||
var rowInput = selectedRow.querySelector('.abha-input');
|
||||
|
|
|
|||
|
|
@ -120,7 +120,9 @@ window.AbhaVerifyModal = (function () {
|
|||
var raw = response && (response.error_text || response.message)
|
||||
? String(response.error_text || response.message)
|
||||
: fallback;
|
||||
if (/UIDAI Error code/i.test(raw) || /ABDM-1204/i.test(raw) || /attempts.*exceeded/i.test(raw) || /invalid.*otp/i.test(raw) || /incorrect.*otp/i.test(raw) || /otp.*match/i.test(raw) || /otp.*expired/i.test(raw)) {
|
||||
if (/loginHint/i.test(raw) || /Invalid Login Hint/i.test(raw)) {
|
||||
raw = 'ABDM gateway rejected login hint: OTP verification by ABHA Address is not supported directly by ABDM. Please verify using the 14-digit ABHA Number or Find ABHA via Mobile.';
|
||||
} else if (/UIDAI Error code/i.test(raw) || /ABDM-1204/i.test(raw) || /attempts.*exceeded/i.test(raw) || /invalid.*otp/i.test(raw) || /incorrect.*otp/i.test(raw) || /otp.*match/i.test(raw) || /otp.*expired/i.test(raw)) {
|
||||
if (/attempts.*exceeded/i.test(raw) || /403/.test(raw)) {
|
||||
raw = 'Incorrect OTP. Maximum number of attempts exceeded. Please generate a fresh OTP and try again.';
|
||||
} else {
|
||||
|
|
@ -205,10 +207,14 @@ window.AbhaVerifyModal = (function () {
|
|||
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; }
|
||||
lookupResponse = response;
|
||||
var account = response.account || {};
|
||||
var hasAbhaNumber = !!(account.abha_number || account.ABHANumber || account.abhaNumber || response.abha_id || response.abha_number);
|
||||
$('#abhaVerifyFoundName').text(account.name || 'ABHA account found');
|
||||
$('#abhaVerifyFoundIdentity').text(account.abha_address || account.abha_number || identifier);
|
||||
renderAuthMethods(response);
|
||||
showStep(2);
|
||||
if (lookupType === 'address' && !hasAbhaNumber) {
|
||||
alertBox('info', '<i class="bi bi-info-circle me-1"></i>Found ABHA Address <strong>' + escapeHtml(account.abha_address || identifier) + '</strong>. If ABDM rejects profile OTP for this address, verify using the patient\'s 14-digit ABHA Number or "Find ABHA via Mobile".');
|
||||
}
|
||||
}, 'json').fail(function (xhr) {
|
||||
button.prop('disabled', false).html('<i class="bi bi-search me-1"></i>Find Account');
|
||||
alertBox('danger', apiMessage(xhr.responseJSON, 'Unable to look up the ABHA account.'));
|
||||
|
|
@ -218,8 +224,10 @@ window.AbhaVerifyModal = (function () {
|
|||
var method = $('input[name="abhaVerifyAuth"]:checked').val();
|
||||
if (!method || !lookupResponse) return;
|
||||
var account = lookupResponse.account || {};
|
||||
var abhaId = account.abha_number || account.ABHANumber || account.abhaNumber || lookupResponse.abha_id || lookupResponse.abha_number || '';
|
||||
var abhaAddress = account.abha_address || account.abhaAddress || lookupResponse.abha_address || '';
|
||||
var button = $('#abhaVerifySendOtpBtn').prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Sending');
|
||||
$.post('<?= base_url('abha/register/login/request-otp') ?>', { txn_id:lookupResponse.txn_id, auth_method:method, abha_id:account.abha_number || '', abha_address:account.abha_address || '', '<?= csrf_token() ?>':csrf() }, function (response) {
|
||||
$.post('<?= base_url('abha/register/login/request-otp') ?>', { txn_id:lookupResponse.txn_id, auth_method:method, abha_id:abhaId, abha_address:abhaAddress, '<?= csrf_token() ?>':csrf() }, function (response) {
|
||||
button.prop('disabled', false).html('<i class="bi bi-send me-1"></i>Send OTP');
|
||||
if (!response || response.ok != 1) { alertBox('danger', apiMessage(response, 'Unable to send OTP.')); return; }
|
||||
lookupResponse.txn_id = response.txn_id || lookupResponse.txn_id;
|
||||
|
|
|
|||
28
tests/unit/Abdm/AbdmTaskBoardBackfillTest.php
Normal file
28
tests/unit/Abdm/AbdmTaskBoardBackfillTest.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use App\Controllers\AbdmTaskBoard;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
|
||||
final class AbdmTaskBoardBackfillTest extends CIUnitTestCase
|
||||
{
|
||||
public function testIsValidAbhaNumberAcceptsCleanAndFormattedAndAddress(): void
|
||||
{
|
||||
$controller = (new ReflectionClass(AbdmTaskBoard::class))->newInstanceWithoutConstructor();
|
||||
$method = new ReflectionMethod(AbdmTaskBoard::class, 'isValidAbhaNumber');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// 14 digits clean
|
||||
$this->assertTrue($method->invoke($controller, '91747451787143'));
|
||||
// 14 digits with standard hyphens
|
||||
$this->assertTrue($method->invoke($controller, '91-7474-5178-7143'));
|
||||
// ABHA address
|
||||
$this->assertTrue($method->invoke($controller, 'singhkeshav301976@sbx'));
|
||||
$this->assertTrue($method->invoke($controller, 'patient@abdm'));
|
||||
|
||||
// Invalid cases
|
||||
$this->assertFalse($method->invoke($controller, ''));
|
||||
$this->assertFalse($method->invoke($controller, '0'));
|
||||
$this->assertFalse($method->invoke($controller, '12345'));
|
||||
$this->assertFalse($method->invoke($controller, 'invalid_address'));
|
||||
}
|
||||
}
|
||||
|
|
@ -62,4 +62,42 @@ final class AbhaOtpErrorHandlingTest extends CIUnitTestCase
|
|||
$result = $this->extractMethod->invoke($this->controller, $payload, 'Please enter a valid OTP. Entered OTP is either expired or incorrect.');
|
||||
$this->assertSame('Incorrect OTP', $result);
|
||||
}
|
||||
|
||||
public function testExtractBridgeErrorTextSkipsGenericUpstreamErrorAndExtractsLoginHint(): void
|
||||
{
|
||||
$payload = [
|
||||
'ok' => 0,
|
||||
'error' => [
|
||||
'source' => 'abdm_gateway',
|
||||
'code' => 'ABDM_UPSTREAM_ERROR',
|
||||
'message' => 'ABDM gateway returned an error.',
|
||||
'http_status' => 400,
|
||||
],
|
||||
'data' => [
|
||||
'loginHint' => 'Invalid Login Hint',
|
||||
'timestamp' => '2026-10-01 16:20:23',
|
||||
],
|
||||
];
|
||||
|
||||
$result = $this->extractMethod->invoke($this->controller, $payload, 'The ABDM Bridge could not send the OTP.');
|
||||
$this->assertStringContainsString('ABDM gateway rejected login hint', $result);
|
||||
$this->assertStringContainsString('14-digit ABHA Number', $result);
|
||||
$this->assertStringNotContainsString('ABDM gateway returned an error.', $result);
|
||||
}
|
||||
|
||||
public function testExtractBridgeErrorTextPreservesGenericWhenNoFieldErrors(): void
|
||||
{
|
||||
$payload = [
|
||||
'ok' => 0,
|
||||
'error' => [
|
||||
'source' => 'abdm_gateway',
|
||||
'code' => 'ABDM_UPSTREAM_ERROR',
|
||||
'message' => 'ABDM gateway returned an error.',
|
||||
'http_status' => 400,
|
||||
],
|
||||
];
|
||||
|
||||
$result = $this->extractMethod->invoke($this->controller, $payload, 'The ABDM Bridge could not send the OTP.');
|
||||
$this->assertSame('ABDM gateway returned an error.', $result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user