feat(abdm): add Find ABHA by Aadhaar Number flow (M1 VRFY_ABHA_401 to VRFY_ABHA_405)
This commit is contained in:
parent
39017a43cf
commit
e59bd8d2b7
|
|
@ -843,6 +843,8 @@ $routes->post('abha/find/mobile/search', 'Abha::findMobileSearch', ['filter' =>
|
|||
$routes->post('abha/find/mobile/request-otp', 'Abha::findMobileRequestOtp', ['filter' => 'permission:abdm.abha.create']);
|
||||
$routes->post('abha/find/mobile/verify-otp', 'Abha::findMobileVerifyOtp', ['filter' => 'permission:abdm.abha.create']);
|
||||
$routes->post('abha/find/mobile/select-account', 'Abha::selectAccount', ['filter' => 'permission:abdm.abha.create']);
|
||||
$routes->post('abha/find/aadhaar/request-otp', 'Abha::findAadhaarRequestOtp', ['filter' => 'permission:abdm.abha.create']);
|
||||
$routes->post('abha/find/aadhaar/verify-otp', 'Abha::findAadhaarVerifyOtp', ['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']);
|
||||
|
|
|
|||
|
|
@ -1082,6 +1082,321 @@ class Abha extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ABDM M1 Find ABHA via Aadhaar Flow
|
||||
// TEST POINT: 12.1 VRFY_ABHA_401 to VRFY_ABHA_405
|
||||
// FLOW: AADHAAR NUMBER -> OTP -> COMPLETE PROFILE FETCHED / CREATE ABHA
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* VRFY_ABHA_401: Request OTP using 12-digit Aadhaar Number
|
||||
* POST abha/find/aadhaar/request-otp
|
||||
*/
|
||||
public function findAadhaarRequestOtp()
|
||||
{
|
||||
if (! $this->request->isAJAX()) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['ok' => 0, 'error_text' => 'Invalid request']);
|
||||
}
|
||||
|
||||
$aadhaar = preg_replace('/\D/', '', trim((string) ($this->request->getPost('aadhaar') ?? '')));
|
||||
if (strlen($aadhaar) !== 12) {
|
||||
return $this->response->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => 'Aadhaar Number is not valid. Please enter a valid 12-digit Aadhaar Number.',
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$helper = new \App\Libraries\Abdm\AbdmV3DirectHelper();
|
||||
$result = $helper->requestLoginOtpByAadhaar($aadhaar);
|
||||
|
||||
if (! empty($result['ok']) && (int) $result['ok'] === 1) {
|
||||
return $this->response->setJSON([
|
||||
'ok' => 1,
|
||||
'txn_id' => $result['txn_id'] ?? '',
|
||||
'message' => $result['message'] ?? 'OTP sent to Aadhaar linked mobile number.',
|
||||
'resend_after' => $result['resend_after'] ?? 60,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => $result['error_text'] ?? 'Failed to send OTP to Aadhaar linked mobile.',
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', '[ABHA] findAadhaarRequestOtp exception: ' . $e->getMessage());
|
||||
return $this->response->setStatusCode(500)->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* VRFY_ABHA_402, 403, 404, 405: Verify Aadhaar OTP and fetch complete ABHA profile + card
|
||||
* POST abha/find/aadhaar/verify-otp
|
||||
*/
|
||||
public function findAadhaarVerifyOtp()
|
||||
{
|
||||
if (! $this->request->isAJAX()) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['ok' => 0, 'error_text' => 'Invalid request']);
|
||||
}
|
||||
|
||||
$txnId = trim((string) ($this->request->getPost('txn_id') ?? $this->request->getPost('txnId') ?? ''));
|
||||
$otp = preg_replace('/\D/', '', trim((string) ($this->request->getPost('otp') ?? '')));
|
||||
|
||||
if ($txnId === '' || strlen($otp) !== 6) {
|
||||
return $this->response->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => 'Transaction ID and 6-digit OTP are required.',
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$helper = new \App\Libraries\Abdm\AbdmV3DirectHelper();
|
||||
$result = $helper->verifyLoginOtpByAadhaar($txnId, $otp);
|
||||
|
||||
// VRFY_ABHA_403: No ABHA user registered with this Aadhaar Number
|
||||
if (! empty($result['no_abha_found'])) {
|
||||
return $this->response->setJSON([
|
||||
'ok' => 0,
|
||||
'no_abha_found' => true,
|
||||
'error_text' => 'NO ABHA user registered with this Aadhaar Number',
|
||||
]);
|
||||
}
|
||||
|
||||
if (empty($result['ok']) || (int) $result['ok'] !== 1) {
|
||||
return $this->response->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => $result['error_text'] ?? 'Incorrect OTP',
|
||||
]);
|
||||
}
|
||||
|
||||
// VRFY_ABHA_404: ABHA exists -> extract profile details & card
|
||||
$fullProfile = is_array($result['full_profile'] ?? null) ? $result['full_profile'] : [];
|
||||
$account = is_array($result['account'] ?? null) ? $result['account'] : [];
|
||||
$userToken = (string) ($result['token'] ?? '');
|
||||
|
||||
$firstNonEmpty = static function (array $values): string {
|
||||
foreach ($values as $value) {
|
||||
if (is_scalar($value) && trim((string) $value) !== '') {
|
||||
return trim((string) $value);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
$abhaNum = $firstNonEmpty([
|
||||
$account['ABHANumber'] ?? null,
|
||||
$account['abhaNumber'] ?? null,
|
||||
$account['abha_number'] ?? null,
|
||||
$account['abha_id'] ?? null,
|
||||
$fullProfile['ABHANumber'] ?? null,
|
||||
$fullProfile['abhaNumber'] ?? null,
|
||||
$fullProfile['abha_id'] ?? null,
|
||||
]);
|
||||
|
||||
$abhaAddress = $firstNonEmpty([
|
||||
$account['preferredAbhaAddress'] ?? null,
|
||||
$account['preferredAddress'] ?? null,
|
||||
$account['abhaAddress'] ?? null,
|
||||
$account['abha_address'] ?? null,
|
||||
$fullProfile['preferredAbhaAddress'] ?? null,
|
||||
$fullProfile['preferredAddress'] ?? null,
|
||||
$fullProfile['abhaAddress'] ?? null,
|
||||
$fullProfile['abha_address'] ?? null,
|
||||
]);
|
||||
|
||||
$name = $firstNonEmpty([
|
||||
$account['name'] ?? null,
|
||||
$account['fullName'] ?? null,
|
||||
$account['full_name'] ?? null,
|
||||
$fullProfile['name'] ?? null,
|
||||
trim(($fullProfile['firstName'] ?? '') . ' ' . ($fullProfile['middleName'] ?? '') . ' ' . ($fullProfile['lastName'] ?? '')),
|
||||
]);
|
||||
|
||||
$genderRaw = $firstNonEmpty([
|
||||
$account['gender'] ?? null,
|
||||
$fullProfile['gender'] ?? null,
|
||||
]);
|
||||
$genderUpper = strtoupper($genderRaw);
|
||||
if ($genderUpper === 'M' || $genderUpper === '1' || $genderUpper === 'MALE') {
|
||||
$gender = 'Male';
|
||||
} elseif ($genderUpper === 'F' || $genderUpper === '2' || $genderUpper === 'FEMALE') {
|
||||
$gender = 'Female';
|
||||
} elseif ($genderUpper === 'O' || $genderUpper === '3' || $genderUpper === 'OTHER') {
|
||||
$gender = 'Other';
|
||||
} else {
|
||||
$gender = $genderRaw;
|
||||
}
|
||||
|
||||
$dob = $firstNonEmpty([
|
||||
$account['dob'] ?? null,
|
||||
$account['dateOfBirth'] ?? null,
|
||||
$account['date_of_birth'] ?? null,
|
||||
$fullProfile['dob'] ?? null,
|
||||
$fullProfile['dateOfBirth'] ?? null,
|
||||
$fullProfile['date_of_birth'] ?? null,
|
||||
]);
|
||||
if ($dob === '') {
|
||||
$day = $firstNonEmpty([$account['dayOfBirth'] ?? null, $account['day_of_birth'] ?? null, $fullProfile['dayOfBirth'] ?? null, $fullProfile['day_of_birth'] ?? null]);
|
||||
$month = $firstNonEmpty([$account['monthOfBirth'] ?? null, $account['month_of_birth'] ?? null, $fullProfile['monthOfBirth'] ?? null, $fullProfile['month_of_birth'] ?? null]);
|
||||
$year = $firstNonEmpty([$account['yearOfBirth'] ?? null, $account['year_of_birth'] ?? null, $fullProfile['yearOfBirth'] ?? null, $fullProfile['year_of_birth'] ?? null]);
|
||||
if ($year !== '' && $month !== '' && $day !== '') {
|
||||
$dob = sprintf('%04d-%02d-%02d', (int) $year, (int) $month, (int) $day);
|
||||
} elseif ($year !== '') {
|
||||
$dob = $year;
|
||||
}
|
||||
}
|
||||
|
||||
$profileMobile = $firstNonEmpty([
|
||||
$account['mobile'] ?? null,
|
||||
$account['mobileNumber'] ?? null,
|
||||
$account['mobile_number'] ?? null,
|
||||
$fullProfile['mobile'] ?? null,
|
||||
$fullProfile['mobileNumber'] ?? null,
|
||||
]);
|
||||
|
||||
$photo = $firstNonEmpty([
|
||||
$account['profilePhoto'] ?? null,
|
||||
$account['profile_photo'] ?? null,
|
||||
$fullProfile['profilePhoto'] ?? null,
|
||||
$fullProfile['profile_photo'] ?? null,
|
||||
$fullProfile['photo'] ?? null,
|
||||
$fullProfile['kycPhoto'] ?? null,
|
||||
]);
|
||||
|
||||
$address = $firstNonEmpty([
|
||||
$fullProfile['address'] ?? null,
|
||||
$fullProfile['addressLine'] ?? null,
|
||||
$fullProfile['address_line'] ?? null,
|
||||
$account['address'] ?? null,
|
||||
$account['addressLine'] ?? null,
|
||||
]);
|
||||
|
||||
$districtName = $firstNonEmpty([
|
||||
$fullProfile['district'] ?? null,
|
||||
$fullProfile['districtName'] ?? null,
|
||||
$fullProfile['district_name'] ?? null,
|
||||
$account['district'] ?? null,
|
||||
$account['districtName'] ?? null,
|
||||
]);
|
||||
|
||||
$stateName = $firstNonEmpty([
|
||||
$fullProfile['state'] ?? null,
|
||||
$fullProfile['stateName'] ?? null,
|
||||
$fullProfile['state_name'] ?? null,
|
||||
$account['state'] ?? null,
|
||||
$account['stateName'] ?? null,
|
||||
]);
|
||||
|
||||
$zip = $firstNonEmpty([
|
||||
$fullProfile['pinCode'] ?? null,
|
||||
$fullProfile['pincode'] ?? null,
|
||||
$fullProfile['pin'] ?? null,
|
||||
$account['pinCode'] ?? null,
|
||||
$account['pincode'] ?? null,
|
||||
]);
|
||||
|
||||
$email = $firstNonEmpty([
|
||||
$fullProfile['email'] ?? null,
|
||||
$account['email'] ?? null,
|
||||
]);
|
||||
|
||||
$cardBase64 = (string) ($result['card_base64'] ?? '');
|
||||
|
||||
// Fallback to Bridge card endpoint if not retrieved directly
|
||||
if ($cardBase64 === '' && ($abhaNum !== '' || $abhaAddress !== '')) {
|
||||
try {
|
||||
$bridge = \App\Libraries\Abdm\AbdmConnectorFactory::make();
|
||||
$cardResult = $bridge->fetchOfficialAbhaCard([
|
||||
'abha_number' => $abhaNum,
|
||||
'abha_address' => $abhaAddress,
|
||||
'token' => $userToken,
|
||||
'x_token' => $userToken,
|
||||
]);
|
||||
if (! empty($cardResult['ok']) && ! empty($cardResult['card_base64'])) {
|
||||
$cardBase64 = (string) $cardResult['card_base64'];
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
log_message('warning', '[ABHA] Bridge card fetch fallback failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$abhaMeta = [
|
||||
'abha_address' => $abhaAddress,
|
||||
'profile_photo' => $photo,
|
||||
'verified_status' => 'ACTIVE',
|
||||
'verification_type' => 'VERIFIED',
|
||||
'kyc_verified' => true,
|
||||
'mobile_verified' => true,
|
||||
'address' => $address,
|
||||
'district' => $districtName,
|
||||
'state' => $stateName,
|
||||
'zip' => $zip,
|
||||
'email' => $email,
|
||||
];
|
||||
|
||||
$patientInfo = $this->tryAutoLinkByDirectMatch($abhaNum, $name, $profileMobile, $gender, $dob, $abhaMeta);
|
||||
|
||||
$responseBase = [
|
||||
'ok' => 1,
|
||||
'txn_id' => $result['txn_id'] ?? $txnId,
|
||||
'token' => $result['token'] ?? '',
|
||||
'accounts' => $result['accounts'] ?? [],
|
||||
'card_base64' => $cardBase64,
|
||||
'card_content_type' => 'image/png',
|
||||
'card_source' => $cardBase64 !== '' ? 'abdm' : 'generated',
|
||||
'card_message' => $cardBase64 !== '' ? 'Official ABHA Card from ABDM' : '',
|
||||
'abha_number' => $abhaNum,
|
||||
'name' => $name,
|
||||
'photo' => $photo,
|
||||
'mobile' => $profileMobile,
|
||||
'gender' => $gender,
|
||||
'dob' => $dob,
|
||||
'abha_address' => $abhaAddress,
|
||||
'verified_status' => 'ACTIVE',
|
||||
'verification_type' => 'VERIFIED',
|
||||
'kyc_verified' => true,
|
||||
'mobile_verified' => true,
|
||||
'address' => $address,
|
||||
'district' => $districtName,
|
||||
'state' => $stateName,
|
||||
'zip' => $zip,
|
||||
'email' => $email,
|
||||
];
|
||||
|
||||
if ($patientInfo !== null) {
|
||||
return $this->response->setJSON($responseBase + [
|
||||
'need_confirmation' => false,
|
||||
'patient_id' => $patientInfo['patient_id'],
|
||||
'p_code' => $patientInfo['p_code'],
|
||||
'is_new_patient' => $patientInfo['is_new'],
|
||||
]);
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$fields = $db->getFieldNames('patient_master') ?? [];
|
||||
$abhaField = $this->resolveAbhaFieldName($fields);
|
||||
$abhaNumClean = preg_replace('/\D/', '', $abhaNum);
|
||||
$candidates = $this->findMatchingCandidates($db, $fields, $name, $profileMobile, $gender, $dob, '', $abhaField, $abhaNumClean);
|
||||
|
||||
return $this->response->setJSON($responseBase + [
|
||||
'need_confirmation' => true,
|
||||
'patient_id' => 0,
|
||||
'p_code' => '',
|
||||
'is_new_patient' => null,
|
||||
'candidates' => $candidates,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', '[ABHA] findAadhaarVerifyOtp exception: ' . $e->getMessage());
|
||||
return $this->response->setStatusCode(500)->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Step 4 — ABHA address suggestions / assignment
|
||||
// POST abha/create/address_suggestions, POST abha/create/address
|
||||
|
|
|
|||
|
|
@ -418,6 +418,173 @@ class AbdmV3DirectHelper
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* M1 12.1 VRFY_ABHA_401: Request OTP using 12-digit Aadhaar Number.
|
||||
* Upstream: POST /abha/api/v3/profile/login/request/otp
|
||||
*
|
||||
* @return array{ok: int, txn_id?: string, message?: string, error_text?: string, resend_after?: int}
|
||||
*/
|
||||
public function requestLoginOtpByAadhaar(string $aadhaarNumber): array
|
||||
{
|
||||
$digits = preg_replace('/\D/', '', $aadhaarNumber);
|
||||
if (strlen($digits) !== 12) {
|
||||
return ['ok' => 0, 'error_text' => 'Aadhaar Number is not valid.'];
|
||||
}
|
||||
|
||||
try {
|
||||
$token = $this->getSessionToken();
|
||||
if ($token === '') {
|
||||
return ['ok' => 0, 'error_text' => 'Could not obtain ABDM session token.'];
|
||||
}
|
||||
|
||||
$encAadhaar = $this->encrypt($digits);
|
||||
$payload = [
|
||||
'scope' => ['abha-login', 'aadhaar-verify'],
|
||||
'loginHint' => 'aadhaar',
|
||||
'loginId' => $encAadhaar,
|
||||
'otpSystem' => 'aadhaar',
|
||||
];
|
||||
|
||||
$resp = $this->curlCall($this->otpUrl, 'POST', $payload, [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'REQUEST-ID: ' . $this->generateUuid(),
|
||||
'TIMESTAMP: ' . gmdate('Y-m-d\TH:i:s.000\Z'),
|
||||
'X-Client-ID: ' . $this->clientId,
|
||||
]);
|
||||
|
||||
if ($resp['code'] >= 200 && $resp['code'] < 300 && ! empty($resp['json'])) {
|
||||
$newTxnId = (string) ($resp['json']['txnId'] ?? '');
|
||||
$msg = (string) ($resp['json']['message'] ?? 'OTP sent to Aadhaar linked mobile number.');
|
||||
return [
|
||||
'ok' => 1,
|
||||
'txn_id' => $newTxnId,
|
||||
'message' => $msg,
|
||||
'resend_after' => 60,
|
||||
];
|
||||
}
|
||||
|
||||
$errMsg = (string) ($resp['json']['message'] ?? $resp['json']['error']['message'] ?? '');
|
||||
if ($errMsg === '') {
|
||||
$errMsg = 'Failed to request Aadhaar OTP from ABDM.';
|
||||
}
|
||||
if (stripos($errMsg, 'invalid') !== false && stripos($errMsg, 'aadhaar') !== false) {
|
||||
$errMsg = 'Aadhaar Number is not valid.';
|
||||
}
|
||||
|
||||
return ['ok' => 0, 'error_text' => $errMsg, 'raw' => $resp['raw']];
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', '[AbdmV3DirectHelper] requestLoginOtpByAadhaar exception: ' . $e->getMessage());
|
||||
return ['ok' => 0, 'error_text' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* M1 12.1 VRFY_ABHA_402, 403, 404: Verify Aadhaar OTP and fetch complete ABHA profile + card.
|
||||
* Upstream: POST /abha/api/v3/profile/login/verify
|
||||
*
|
||||
* @return array{ok: int, txn_id?: string, token?: string, accounts?: array, account?: array, full_profile?: array, card_base64?: string, no_abha_found?: bool, error_text?: string}
|
||||
*/
|
||||
public function verifyLoginOtpByAadhaar(string $otpTxnId, string $otp): array
|
||||
{
|
||||
$digits = preg_replace('/\D/', '', $otp);
|
||||
if (strlen($digits) !== 6) {
|
||||
return ['ok' => 0, 'error_text' => 'Enter a valid 6-digit OTP.'];
|
||||
}
|
||||
|
||||
try {
|
||||
$token = $this->getSessionToken();
|
||||
if ($token === '') {
|
||||
return ['ok' => 0, 'error_text' => 'Could not obtain ABDM session token.'];
|
||||
}
|
||||
|
||||
$encOtp = $this->encrypt($digits);
|
||||
$payload = [
|
||||
'scope' => ['abha-login', 'aadhaar-verify'],
|
||||
'authData' => [
|
||||
'authMethods' => ['otp'],
|
||||
'otp' => [
|
||||
'txnId' => $otpTxnId,
|
||||
'otpValue' => $encOtp,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$resp = $this->curlCall($this->verifyUrl, 'POST', $payload, [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'REQUEST-ID: ' . $this->generateUuid(),
|
||||
'TIMESTAMP: ' . gmdate('Y-m-d\TH:i:s.000\Z'),
|
||||
'X-Client-ID: ' . $this->clientId,
|
||||
]);
|
||||
|
||||
if ($resp['code'] >= 200 && $resp['code'] < 300 && ! empty($resp['json'])) {
|
||||
$authResult = strtolower((string) ($resp['json']['authResult'] ?? ''));
|
||||
if ($authResult === 'failed') {
|
||||
$failMsg = (string) ($resp['json']['message'] ?? 'Incorrect OTP.');
|
||||
return ['ok' => 0, 'error_text' => $failMsg];
|
||||
}
|
||||
|
||||
$userToken = (string) ($resp['json']['token'] ?? '');
|
||||
$accounts = (array) ($resp['json']['accounts'] ?? []);
|
||||
$firstAcc = isset($accounts[0]) && is_array($accounts[0]) ? $accounts[0] : [];
|
||||
|
||||
// Check VRFY_ABHA_403: No ABHA user registered with this Aadhaar Number
|
||||
if (empty($userToken) && empty($accounts)) {
|
||||
return [
|
||||
'ok' => 0,
|
||||
'no_abha_found' => true,
|
||||
'error_text' => 'NO ABHA user registered with this Aadhaar Number',
|
||||
];
|
||||
}
|
||||
|
||||
$fullProfile = [];
|
||||
$cardBase64 = '';
|
||||
if ($userToken !== '') {
|
||||
$fullProfile = $this->fetchProfileDetails($userToken, $token);
|
||||
$cardBase64 = $this->fetchCardPng($userToken, $token);
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => 1,
|
||||
'txn_id' => (string) ($resp['json']['txnId'] ?? $otpTxnId),
|
||||
'token' => $userToken,
|
||||
'accounts' => $accounts,
|
||||
'account' => $firstAcc,
|
||||
'full_profile' => $fullProfile,
|
||||
'card_base64' => $cardBase64,
|
||||
];
|
||||
}
|
||||
|
||||
$errMsg = (string) ($resp['json']['message'] ?? $resp['json']['error']['message'] ?? '');
|
||||
|
||||
// Check if error is VRFY_ABHA_403: ABHA does not exist for this Aadhaar
|
||||
if (stripos($errMsg, 'no abha') !== false ||
|
||||
stripos($errMsg, 'not found') !== false ||
|
||||
stripos($errMsg, 'not exist') !== false ||
|
||||
stripos($errMsg, 'no user') !== false ||
|
||||
stripos($errMsg, 'no account') !== false) {
|
||||
return [
|
||||
'ok' => 0,
|
||||
'no_abha_found' => true,
|
||||
'error_text' => 'NO ABHA user registered with this Aadhaar Number',
|
||||
'raw' => $resp['raw'],
|
||||
];
|
||||
}
|
||||
|
||||
if (stripos($errMsg, 'otp') !== false || stripos($errMsg, 'invalid') !== false || stripos($errMsg, 'incorrect') !== false) {
|
||||
$errMsg = 'Incorrect OTP';
|
||||
}
|
||||
|
||||
if ($errMsg === '') {
|
||||
$errMsg = 'OTP verification failed.';
|
||||
}
|
||||
|
||||
return ['ok' => 0, 'error_text' => $errMsg, 'raw' => $resp['raw']];
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', '[AbdmV3DirectHelper] verifyLoginOtpByAadhaar exception: ' . $e->getMessage());
|
||||
return ['ok' => 0, 'error_text' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch full profile details using the profile X-Token.
|
||||
* Upstream: GET /abha/api/v3/profile/account
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
<?= view('partials/abha_create_modal') ?>
|
||||
<?= view('partials/abha_qr_modal') ?>
|
||||
<?= view('partials/abha_mobile_modal') ?>
|
||||
<?= view('partials/abha_aadhaar_modal') ?>
|
||||
<?= view('partials/abdm_hip_link_modal') ?>
|
||||
<section class="content">
|
||||
<div class="row">
|
||||
|
|
@ -447,6 +448,7 @@
|
|||
<button type="button" class="btn btn-success" onclick="abhaRegSelectMethod('number')"><i class="bi bi-person-vcard me-1"></i>By ABHA Number or ID</button>
|
||||
<button type="button" class="btn btn-outline-primary" onclick="abhaRegSelectMethod('address')"><i class="bi bi-at me-1"></i>By ABHA Address</button>
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="abhaRegSelectMethod('mobile')"><i class="bi bi-phone me-1"></i>Find ABHA via Mobile</button>
|
||||
<button type="button" class="btn btn-outline-info" onclick="abhaRegSelectMethod('aadhaar')"><i class="bi bi-fingerprint me-1"></i>Find ABHA via Aadhaar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1404,7 +1406,8 @@
|
|||
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: 'Find ABHA via Mobile', sub: 'Find an ABHA linked to this mobile number' }
|
||||
mobile: { icon: '📱', title: 'Find ABHA via Mobile', sub: 'Find an ABHA linked to this mobile number' },
|
||||
aadhaar: { icon: '🪪', title: 'Find ABHA via Aadhaar', sub: 'Retrieve ABHA using 12-digit Aadhaar number' }
|
||||
};
|
||||
|
||||
window.abhaRegSelectMethod = function(method) {
|
||||
|
|
@ -1428,10 +1431,29 @@
|
|||
} else if (method === 'mobile') {
|
||||
$('#abhareg_panel_mobile').addClass('d-none');
|
||||
openAbhaMobileVerification();
|
||||
} else if (method === 'aadhaar') {
|
||||
$('#abhareg_panel_mobile').addClass('d-none');
|
||||
openAbhaAadhaarVerification();
|
||||
} else if (method === 'qr') { openAbhaQrScan(); }
|
||||
if (method === 'facility') { loadFacilityQr(false); }
|
||||
};
|
||||
|
||||
function openAbhaAadhaarVerification() {
|
||||
if (!window.AbhaAadhaarModal) {
|
||||
alert('ABHA Aadhaar verification modal is unavailable. Reload the page and try again.');
|
||||
return;
|
||||
}
|
||||
window.AbhaAadhaarModal.open('', function(profile) {
|
||||
if (profile && profile.need_confirmation === false && Number(profile.patient_id || 0) > 0) {
|
||||
showRegResult(profile);
|
||||
return;
|
||||
}
|
||||
window.AbhaPatientMatchModal.open(profile, profile.candidates || [], function(finalResponse) {
|
||||
showRegResult(finalResponse);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function openAbhaMobileVerification() {
|
||||
if (!window.AbhaMobileModal) {
|
||||
alert('ABHA mobile verification modal is unavailable. Reload the page and try again.');
|
||||
|
|
|
|||
553
app/Views/partials/abha_aadhaar_modal.php
Normal file
553
app/Views/partials/abha_aadhaar_modal.php
Normal file
|
|
@ -0,0 +1,553 @@
|
|||
<?php
|
||||
/**
|
||||
* Partial: "Find ABHA by Aadhaar Number" modal.
|
||||
*
|
||||
* Compliant with ABDM M1 Guidelines (12.1 VRFY_ABHA_401 to VRFY_ABHA_405):
|
||||
* VRFY_ABHA_401: Fetch ABHA details using Aadhaar Number
|
||||
* VRFY_ABHA_402: Incorrect OTP handling
|
||||
* VRFY_ABHA_403: ABHA Details not exists to Aadhaar Number -> Provision to create ABHA
|
||||
* VRFY_ABHA_404: ABHA Details exists to Aadhaar Number -> Display Profile & Card
|
||||
* VRFY_ABHA_405: Resend OTP Functionality (60s timer, max 2 times)
|
||||
*
|
||||
* Include once per page: <?= view('partials/abha_aadhaar_modal') ?>
|
||||
* Then call: window.AbhaAadhaarModal.open(prefillAadhaar, function (profile) { ... });
|
||||
*/
|
||||
?>
|
||||
<style>
|
||||
.abha-aadhaar-modal .modal-content { border: 0; border-radius: 12px; overflow: hidden; }
|
||||
.abha-aadhaar-modal .modal-header { border-bottom: 1px solid #e5eaf1; padding: 18px 22px; }
|
||||
.abha-aadhaar-modal .modal-body { padding: 22px; }
|
||||
.abha-aadhaar-steps { display: flex; justify-content: center; align-items: center; margin-bottom: 24px; }
|
||||
.abha-aadhaar-step { display: flex; align-items: center; color: #8792a3; font-weight: 600; font-size: 0.9rem; }
|
||||
.abha-aadhaar-step:not(:last-child)::after { content: ""; width: 44px; height: 2px; background: #dce3ec; margin: 0 8px; }
|
||||
.abha-aadhaar-step.done:not(:last-child)::after { background: #0d6efd; }
|
||||
.abha-aadhaar-step .step-num { width: 32px; height: 32px; display: grid; place-items: center; border-radius: 50%; background: #eef2f6; font-weight: 700; margin-right: 8px; flex-shrink: 0; }
|
||||
.abha-aadhaar-step.active .step-num { color: #0d6efd; background: #fff; border: 2px solid #0d6efd; box-shadow: 0 0 0 4px #edf2ff; }
|
||||
.abha-aadhaar-step.active { color: #0d6efd; }
|
||||
.abha-aadhaar-step.done .step-num { color: #fff; background: #0d6efd; }
|
||||
.abha-aadhaar-step.done { color: #0d6efd; }
|
||||
.abha-aadhaar-step .step-title { font-weight: 600; white-space: nowrap; }
|
||||
|
||||
.abha-aadhaar-modal .abha-profile-list { margin: 0; }
|
||||
.abha-aadhaar-modal .abha-profile-list > div { display: grid; grid-template-columns: 150px minmax(0, 1fr); gap: 14px; padding: 10px 0; border-bottom: 1px solid #edf0f4; }
|
||||
.abha-aadhaar-modal .abha-profile-list dt { color: #6b7688; font-weight: 500; }
|
||||
.abha-aadhaar-modal .abha-profile-list dd { margin: 0; text-align: right; font-weight: 600; overflow-wrap: anywhere; }
|
||||
.abha-aadhaar-modal .abha-card-preview { min-height: 230px; display: grid; place-items: center; border: 1px solid #dce3ec; background: #f8fafc; border-radius: 8px; padding: 12px; }
|
||||
.abha-aadhaar-modal .abha-card-preview img { max-width: 100%; max-height: 360px; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
.abha-aadhaar-modal .modal-body { padding: 16px; }
|
||||
.abha-aadhaar-step:not(:last-child)::after { width: 20px; }
|
||||
.abha-aadhaar-step .step-num { width: 26px; height: 26px; font-size: 0.8rem; margin-right: 4px; }
|
||||
.abha-aadhaar-modal .abha-profile-list > div { grid-template-columns: 110px minmax(0, 1fr); }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="modal fade abha-aadhaar-modal" id="abhaAadhaarModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title fw-bold text-dark">
|
||||
<i class="bi bi-fingerprint text-primary me-2"></i>Find ABHA by Aadhaar Number
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- 3-Step Stepper -->
|
||||
<div class="abha-aadhaar-steps" aria-label="Verification progress">
|
||||
<div class="abha-aadhaar-step active" data-step="1">
|
||||
<span class="step-num">1</span>
|
||||
<span class="step-title">Aadhaar Number</span>
|
||||
</div>
|
||||
<div class="abha-aadhaar-step" data-step="2">
|
||||
<span class="step-num">2</span>
|
||||
<span class="step-title">Verify OTP</span>
|
||||
</div>
|
||||
<div class="abha-aadhaar-step" data-step="3">
|
||||
<span class="step-num">3</span>
|
||||
<span class="step-title">ABHA Profile</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="abhaAadhaarAlert"></div>
|
||||
|
||||
<!-- Step 1: Aadhaar Number Input -->
|
||||
<section id="abhaAadhaarStep1">
|
||||
<div class="card border-0 bg-light p-4 rounded-3 mb-3">
|
||||
<label class="form-label fw-semibold text-dark fs-6" for="abhaAadhaarInput">
|
||||
Enter 12-Digit Aadhaar Number <span class="text-danger">*</span>
|
||||
</label>
|
||||
<div class="input-group input-group-lg mb-2">
|
||||
<span class="input-group-text bg-white"><i class="bi bi-person-badge"></i></span>
|
||||
<input type="text" class="form-control font-monospace" id="abhaAadhaarInput"
|
||||
maxlength="14" inputmode="numeric" autocomplete="off"
|
||||
placeholder="0000 0000 0000">
|
||||
<button type="button" class="btn btn-primary px-4 fw-semibold" id="abhaAadhaarSendOtpBtn">
|
||||
Send OTP <i class="bi bi-arrow-right ms-1"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text text-muted mb-3">
|
||||
<i class="bi bi-info-circle me-1"></i>System will retrieve the ABHA profile linked to this Aadhaar number via UIDAI OTP authentication.
|
||||
</div>
|
||||
|
||||
<!-- Beneficiary Consent -->
|
||||
<div class="border rounded p-3 bg-white mt-2">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="abhaAadhaarConsent" checked>
|
||||
<label class="form-check-label small text-muted" for="abhaAadhaarConsent">
|
||||
I confirm that the patient/beneficiary has consented to authenticate using their Aadhaar Number to retrieve their ABHA profile as per ABDM guidelines.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Step 2: OTP Verification -->
|
||||
<section id="abhaAadhaarStep2" class="d-none">
|
||||
<div class="card border-0 bg-light p-4 rounded-3 mb-3">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<i class="bi bi-shield-lock fs-4 text-primary"></i>
|
||||
<h6 class="mb-0 fw-bold">Enter Aadhaar OTP</h6>
|
||||
</div>
|
||||
<p class="text-muted small mb-3" id="abhaAadhaarOtpHint">
|
||||
An OTP has been sent to the mobile number registered with your Aadhaar.
|
||||
</p>
|
||||
|
||||
<div class="col-md-6 col-sm-8 mb-3">
|
||||
<label class="form-label fw-semibold" for="abhaAadhaarOtpInput">6-digit OTP <span class="text-danger">*</span></label>
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-text bg-white"><i class="bi bi-key"></i></span>
|
||||
<input type="text" class="form-control text-center fw-bold fs-4" id="abhaAadhaarOtpInput"
|
||||
maxlength="6" inputmode="numeric" autocomplete="one-time-code" placeholder="------">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mt-2">
|
||||
<div>
|
||||
<button type="button" class="btn btn-outline-secondary" id="abhaAadhaarBackBtn">
|
||||
<i class="bi bi-arrow-left me-1"></i>Change Aadhaar
|
||||
</button>
|
||||
<button type="button" class="btn btn-link text-decoration-none px-2" id="abhaAadhaarResendBtn" disabled>
|
||||
Resend OTP in 60s
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-success btn-lg px-4" id="abhaAadhaarVerifyOtpBtn">
|
||||
<i class="bi bi-patch-check me-1"></i>Verify OTP & Fetch Profile
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VRFY_ABHA_403: No ABHA user registered with this Aadhaar container -->
|
||||
<div id="abhaAadhaarNotFoundCard" class="card border-warning bg-warning-subtle p-3 rounded-3 mt-3 d-none">
|
||||
<div class="d-flex align-items-start gap-3">
|
||||
<i class="bi bi-exclamation-triangle-fill text-warning fs-3 mt-1"></i>
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="fw-bold text-dark mb-1">NO ABHA user registered with this Aadhaar Number</h6>
|
||||
<p class="text-muted small mb-2">
|
||||
Aadhaar verification was successful, but no ABHA account exists for this Aadhaar number in the ABDM registry.
|
||||
</p>
|
||||
<button type="button" class="btn btn-primary btn-sm" id="abhaAadhaarCreateNowBtn">
|
||||
<i class="bi bi-plus-circle me-1"></i>Create ABHA with this Aadhaar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Step 3: Verified ABHA Profile Details -->
|
||||
<section id="abhaAadhaarStep3" class="d-none">
|
||||
<div class="alert alert-success py-2 mb-3">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>ABHA profile successfully retrieved and verified via Aadhaar!
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-6">
|
||||
<div class="d-flex align-items-center gap-3 mb-3">
|
||||
<img id="abhaAadhaarPhoto" class="rounded border shadow-sm d-none" alt="ABHA photo" style="width: 80px; height: 80px; object-fit: cover;">
|
||||
<div>
|
||||
<span class="badge bg-success-subtle text-success border border-success-subtle mb-1">
|
||||
<i class="bi bi-patch-check-fill me-1"></i>ABHA Verified
|
||||
</span>
|
||||
<h4 class="mb-0 fw-bold" id="abhaAadhaarProfileName">-</h4>
|
||||
<div class="text-muted small font-monospace" id="abhaAadhaarProfileAddress">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<dl class="abha-profile-list">
|
||||
<div><dt>ABHA Number</dt><dd id="abhaAadhaarProfileNumber">-</dd></div>
|
||||
<div><dt>ABHA Address</dt><dd id="abhaAadhaarProfileId">-</dd></div>
|
||||
<div><dt>Date of Birth</dt><dd id="abhaAadhaarProfileDob">-</dd></div>
|
||||
<div><dt>Gender</dt><dd id="abhaAadhaarProfileGender">-</dd></div>
|
||||
<div><dt>Mobile</dt><dd id="abhaAadhaarProfileMobile">-</dd></div>
|
||||
<div><dt>Address</dt><dd id="abhaAadhaarProfileFullAddress">-</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="abha-card-preview" id="abhaAadhaarCardWrap">
|
||||
<div class="text-center text-muted">
|
||||
<i class="bi bi-card-image fs-1 d-block mb-2 text-primary"></i>
|
||||
<span id="abhaAadhaarCardMessage">Loading ABHA card...</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-outline-primary w-100 mt-2 d-none" id="abhaAadhaarDownloadCard" download="ABHA-Card.png">
|
||||
<i class="bi bi-download me-1"></i>Download Official ABHA Card
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2 mt-4 pt-3 border-top">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-success" id="abhaAadhaarCompareBtn">
|
||||
<i class="bi bi-people me-1"></i>Compare with HMS Patients
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.AbhaAadhaarModal = (function () {
|
||||
'use strict';
|
||||
|
||||
var modal;
|
||||
var onCompletedCallback = null;
|
||||
var currentTxnId = '';
|
||||
var currentAadhaar = '';
|
||||
var verifiedProfile = null;
|
||||
var resendTimer = null;
|
||||
var resendRemaining = 0;
|
||||
var resendAttempts = 0; // VRFY_ABHA_405: max 2 times
|
||||
|
||||
function csrf() {
|
||||
var input = document.querySelector('input[name="<?= csrf_token() ?>"]');
|
||||
return input ? input.value : '<?= csrf_hash() ?>';
|
||||
}
|
||||
|
||||
function escapeHtml(val) {
|
||||
return $('<div>').text(val == null ? '' : String(val)).html();
|
||||
}
|
||||
|
||||
function digits(val) {
|
||||
return String(val || '').replace(/\D/g, '');
|
||||
}
|
||||
|
||||
function formatAadhaar(val) {
|
||||
var d = digits(val).slice(0, 12);
|
||||
var parts = [];
|
||||
for (var i = 0; i < d.length; i += 4) {
|
||||
parts.push(d.slice(i, i + 4));
|
||||
}
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
function validateVerhoeff(num) {
|
||||
num = digits(num);
|
||||
if (num.length !== 12 || num.charAt(0) === '0' || num.charAt(0) === '1') {
|
||||
return false;
|
||||
}
|
||||
var d = [
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
[1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
|
||||
[2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
|
||||
[3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
|
||||
[4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
|
||||
[5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
|
||||
[6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
|
||||
[7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
|
||||
[8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
|
||||
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
|
||||
];
|
||||
var p = [
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
[1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
|
||||
[5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
|
||||
[8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
|
||||
[9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
|
||||
[4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
|
||||
[2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
|
||||
[7, 0, 4, 6, 9, 1, 3, 2, 5, 8]
|
||||
];
|
||||
var c = 0;
|
||||
var inverted = num.split('').reverse();
|
||||
for (var i = 0; i < inverted.length; i++) {
|
||||
c = d[c][p[i % 8][parseInt(inverted[i], 10)]];
|
||||
}
|
||||
return (c === 0);
|
||||
}
|
||||
|
||||
function alertBox(type, msg) {
|
||||
if (!msg) {
|
||||
$('#abhaAadhaarAlert').empty();
|
||||
return;
|
||||
}
|
||||
$('#abhaAadhaarAlert').html('<div class="alert alert-' + type + ' alert-dismissible fade show py-2 mb-3" role="alert">' +
|
||||
msg + '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>');
|
||||
}
|
||||
|
||||
function showStep(step) {
|
||||
$('#abhaAadhaarStep1, #abhaAadhaarStep2, #abhaAadhaarStep3').addClass('d-none');
|
||||
$('#abhaAadhaarStep' + step).removeClass('d-none');
|
||||
$('.abha-aadhaar-step').each(function () {
|
||||
var itemStep = Number($(this).data('step'));
|
||||
$(this).toggleClass('active', itemStep === step).toggleClass('done', itemStep < step);
|
||||
});
|
||||
alertBox('', '');
|
||||
}
|
||||
|
||||
function stopTimer() {
|
||||
if (resendTimer) window.clearInterval(resendTimer);
|
||||
resendTimer = null;
|
||||
}
|
||||
|
||||
function startResendTimer(seconds) {
|
||||
stopTimer();
|
||||
resendRemaining = Math.max(60, Number(seconds) || 60);
|
||||
var button = $('#abhaAadhaarResendBtn').prop('disabled', true);
|
||||
function tick() {
|
||||
if (resendRemaining <= 0) {
|
||||
stopTimer();
|
||||
if (resendAttempts < 2) {
|
||||
var left = 2 - resendAttempts;
|
||||
button.prop('disabled', false).text('Resend OTP (' + left + ' left)');
|
||||
} else {
|
||||
button.prop('disabled', true).text('Resend limit reached (max 2)');
|
||||
}
|
||||
return;
|
||||
}
|
||||
button.text('Resend OTP in ' + resendRemaining + 's');
|
||||
resendRemaining--;
|
||||
}
|
||||
tick();
|
||||
resendTimer = window.setInterval(tick, 1000);
|
||||
}
|
||||
|
||||
function requestOtp(isResend) {
|
||||
var rawAadhaar = digits($('#abhaAadhaarInput').val());
|
||||
if (rawAadhaar.length !== 12 || !validateVerhoeff(rawAadhaar)) {
|
||||
$('#abhaAadhaarInput').addClass('is-invalid').trigger('focus');
|
||||
alertBox('danger', 'Aadhaar Number is not valid. Please enter a valid 12-digit Aadhaar Number.');
|
||||
return;
|
||||
}
|
||||
$('#abhaAadhaarInput').removeClass('is-invalid');
|
||||
|
||||
if (!isResend && !$('#abhaAadhaarConsent').is(':checked')) {
|
||||
alertBox('warning', 'Please provide beneficiary consent to proceed.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isResend) {
|
||||
if (resendAttempts >= 2) {
|
||||
alertBox('warning', 'Maximum 2 resend OTP attempts allowed. Please enter the OTP or try again later.');
|
||||
return;
|
||||
}
|
||||
resendAttempts++;
|
||||
} else {
|
||||
resendAttempts = 0;
|
||||
}
|
||||
|
||||
currentAadhaar = rawAadhaar;
|
||||
var sendBtn = isResend ? $('#abhaAadhaarResendBtn') : $('#abhaAadhaarSendOtpBtn');
|
||||
var originalBtnHtml = sendBtn.html();
|
||||
sendBtn.prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Sending OTP...');
|
||||
|
||||
$.post('<?= base_url('abha/find/aadhaar/request-otp') ?>', {
|
||||
aadhaar: rawAadhaar,
|
||||
'<?= csrf_token() ?>': csrf()
|
||||
}, function (res) {
|
||||
sendBtn.prop('disabled', false).html(originalBtnHtml);
|
||||
if (!res || res.ok != 1) {
|
||||
var err = (res && res.error_text) ? res.error_text : 'Aadhaar Number is not valid.';
|
||||
alertBox('danger', escapeHtml(err));
|
||||
return;
|
||||
}
|
||||
|
||||
currentTxnId = res.txn_id || '';
|
||||
var hintMsg = res.message || 'OTP sent to Aadhaar linked mobile number.';
|
||||
$('#abhaAadhaarOtpHint').html('<i class="bi bi-info-circle me-1"></i>' + escapeHtml(hintMsg));
|
||||
$('#abhaAadhaarOtpInput').val('');
|
||||
$('#abhaAadhaarNotFoundCard').addClass('d-none');
|
||||
showStep(2);
|
||||
startResendTimer(res.resend_after || 60);
|
||||
$('#abhaAadhaarOtpInput').trigger('focus');
|
||||
}, 'json').fail(function (xhr) {
|
||||
sendBtn.prop('disabled', false).html(originalBtnHtml);
|
||||
var err = (xhr.responseJSON && xhr.responseJSON.error_text) ? xhr.responseJSON.error_text : 'Failed to request OTP from ABDM.';
|
||||
alertBox('danger', escapeHtml(err));
|
||||
});
|
||||
}
|
||||
|
||||
function verifyOtp() {
|
||||
var otp = digits($('#abhaAadhaarOtpInput').val());
|
||||
if (otp.length !== 6) {
|
||||
$('#abhaAadhaarOtpInput').addClass('is-invalid').trigger('focus');
|
||||
alertBox('warning', 'Please enter a valid 6-digit OTP.');
|
||||
return;
|
||||
}
|
||||
$('#abhaAadhaarOtpInput').removeClass('is-invalid');
|
||||
|
||||
var btn = $('#abhaAadhaarVerifyOtpBtn').prop('disabled', true)
|
||||
.html('<span class="spinner-border spinner-border-sm me-1"></span>Verifying...');
|
||||
|
||||
$.post('<?= base_url('abha/find/aadhaar/verify-otp') ?>', {
|
||||
txn_id: currentTxnId,
|
||||
otp: otp,
|
||||
aadhaar: currentAadhaar,
|
||||
'<?= csrf_token() ?>': csrf()
|
||||
}, function (res) {
|
||||
btn.prop('disabled', false).html('<i class="bi bi-patch-check me-1"></i>Verify OTP & Fetch Profile');
|
||||
|
||||
// VRFY_ABHA_403: ABHA details do not exist
|
||||
if (res && res.no_abha_found) {
|
||||
alertBox('warning', '<strong>NO ABHA user registered with this Aadhaar Number</strong>');
|
||||
$('#abhaAadhaarNotFoundCard').removeClass('d-none');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!res || res.ok != 1) {
|
||||
var err = (res && res.error_text) ? res.error_text : 'Incorrect OTP';
|
||||
if (/incorrect|invalid|fail/i.test(err)) {
|
||||
err = 'Incorrect OTP';
|
||||
}
|
||||
alertBox('danger', escapeHtml(err));
|
||||
return;
|
||||
}
|
||||
|
||||
// VRFY_ABHA_404: ABHA exists -> render profile
|
||||
renderProfile(res);
|
||||
}, 'json').fail(function (xhr) {
|
||||
btn.prop('disabled', false).html('<i class="bi bi-patch-check me-1"></i>Verify OTP & Fetch Profile');
|
||||
var err = (xhr.responseJSON && xhr.responseJSON.error_text) ? xhr.responseJSON.error_text : 'Incorrect OTP';
|
||||
if (xhr.responseJSON && xhr.responseJSON.no_abha_found) {
|
||||
alertBox('warning', '<strong>NO ABHA user registered with this Aadhaar Number</strong>');
|
||||
$('#abhaAadhaarNotFoundCard').removeClass('d-none');
|
||||
return;
|
||||
}
|
||||
alertBox('danger', escapeHtml(err));
|
||||
});
|
||||
}
|
||||
|
||||
function renderProfile(profile) {
|
||||
verifiedProfile = profile;
|
||||
stopTimer();
|
||||
|
||||
var photo = profile.photo || profile.profile_photo || '';
|
||||
if (photo) {
|
||||
var photoSrc = String(photo).indexOf('data:') === 0 ? photo : 'data:image/jpeg;base64,' + photo;
|
||||
$('#abhaAadhaarPhoto').attr('src', photoSrc).removeClass('d-none');
|
||||
} else {
|
||||
$('#abhaAadhaarPhoto').addClass('d-none');
|
||||
}
|
||||
|
||||
$('#abhaAadhaarProfileName').text(profile.name || '-');
|
||||
$('#abhaAadhaarProfileAddress').text(profile.abha_address || '-');
|
||||
$('#abhaAadhaarProfileNumber').text(profile.abha_number || '-');
|
||||
$('#abhaAadhaarProfileId').text(profile.abha_address || '-');
|
||||
$('#abhaAadhaarProfileDob').text(profile.dob || '-');
|
||||
$('#abhaAadhaarProfileGender').text(profile.gender || '-');
|
||||
$('#abhaAadhaarProfileMobile').text(profile.mobile || '-');
|
||||
|
||||
var fullAddr = [profile.address, profile.district, profile.state, profile.zip].filter(Boolean).join(', ');
|
||||
$('#abhaAadhaarProfileFullAddress').text(fullAddr || '-');
|
||||
|
||||
// ABHA Card render
|
||||
if (profile.card_base64) {
|
||||
var cardSrc = String(profile.card_base64).indexOf('data:') === 0
|
||||
? profile.card_base64
|
||||
: 'data:image/png;base64,' + profile.card_base64;
|
||||
$('#abhaAadhaarCardWrap').html('<img src="' + cardSrc + '" alt="ABHA Card">');
|
||||
$('#abhaAadhaarDownloadCard').attr('href', cardSrc).removeClass('d-none');
|
||||
} else {
|
||||
$('#abhaAadhaarCardWrap').html('<div class="text-center text-muted p-4"><i class="bi bi-card-image fs-1 d-block mb-2"></i>Official ABHA card preview not available</div>');
|
||||
$('#abhaAadhaarDownloadCard').addClass('d-none');
|
||||
}
|
||||
|
||||
showStep(3);
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
$('#abhaAadhaarInput').on('input', function () {
|
||||
var formatted = formatAadhaar($(this).val());
|
||||
$(this).val(formatted);
|
||||
$(this).removeClass('is-invalid');
|
||||
});
|
||||
|
||||
$('#abhaAadhaarOtpInput').on('input', function () {
|
||||
$(this).val(digits($(this).val()).slice(0, 6));
|
||||
$(this).removeClass('is-invalid');
|
||||
});
|
||||
|
||||
$('#abhaAadhaarSendOtpBtn').on('click', function () {
|
||||
requestOtp(false);
|
||||
});
|
||||
|
||||
$('#abhaAadhaarResendBtn').on('click', function () {
|
||||
requestOtp(true);
|
||||
});
|
||||
|
||||
$('#abhaAadhaarVerifyOtpBtn').on('click', function () {
|
||||
verifyOtp();
|
||||
});
|
||||
|
||||
$('#abhaAadhaarBackBtn').on('click', function () {
|
||||
stopTimer();
|
||||
showStep(1);
|
||||
});
|
||||
|
||||
// Switch to Create ABHA when not found (VRFY_ABHA_403)
|
||||
$('#abhaAadhaarCreateNowBtn').on('click', function () {
|
||||
modal.hide();
|
||||
if (window.AbhaCreateModal) {
|
||||
window.AbhaCreateModal.open(function (created) {
|
||||
if (typeof onCompletedCallback === 'function') {
|
||||
onCompletedCallback(created);
|
||||
}
|
||||
});
|
||||
setTimeout(function () {
|
||||
$('#abhaCreateAadhaar').val(formatAadhaar(currentAadhaar)).trigger('input');
|
||||
}, 300);
|
||||
} else {
|
||||
alert('ABHA Create Modal is unavailable.');
|
||||
}
|
||||
});
|
||||
|
||||
$('#abhaAadhaarCompareBtn').on('click', function () {
|
||||
if (!verifiedProfile) return;
|
||||
modal.hide();
|
||||
if (typeof onCompletedCallback === 'function') {
|
||||
onCompletedCallback(verifiedProfile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
open: function (prefillAadhaar, callback) {
|
||||
onCompletedCallback = callback;
|
||||
currentTxnId = '';
|
||||
currentAadhaar = '';
|
||||
verifiedProfile = null;
|
||||
resendAttempts = 0;
|
||||
stopTimer();
|
||||
|
||||
if (!modal) {
|
||||
var el = document.getElementById('abhaAadhaarModal');
|
||||
if (el && window.bootstrap && window.bootstrap.Modal) {
|
||||
modal = new bootstrap.Modal(el);
|
||||
}
|
||||
bindEvents();
|
||||
}
|
||||
|
||||
$('#abhaAadhaarInput').val(prefillAadhaar ? formatAadhaar(prefillAadhaar) : '').removeClass('is-invalid');
|
||||
$('#abhaAadhaarOtpInput').val('').removeClass('is-invalid');
|
||||
$('#abhaAadhaarConsent').prop('checked', true);
|
||||
$('#abhaAadhaarNotFoundCard').addClass('d-none');
|
||||
$('#abhaAadhaarPhoto, #abhaAadhaarDownloadCard').addClass('d-none');
|
||||
showStep(1);
|
||||
|
||||
if (modal) {
|
||||
modal.show();
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
Loading…
Reference in New Issue
Block a user