Implement ABDM M1 multi-ABHA account selection for Mobile OTP login flow

This commit is contained in:
DevSoftTech Server 2026-09-03 16:17:46 +05:30
parent 29ac1ffe66
commit 156d12adba
7 changed files with 375 additions and 9 deletions

View File

@ -814,6 +814,7 @@ $routes->post('abha/create/communication', 'Abha::communication', ['filter' => '
$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/find/mobile/select-account', 'Abha::selectAccount', ['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']);

View File

@ -376,10 +376,19 @@ class Abha extends BaseController
'email' => $email,
];
$rawAccounts = is_array($payload['accounts'] ?? null)
? $payload['accounts']
: (is_array($result['data']['accounts'] ?? null) ? $result['data']['accounts'] : []);
$txnIdOut = (string) ($payload['txnId'] ?? $payload['txn_id'] ?? $result['txn_id'] ?? $result['data']['txnId'] ?? $txnId);
$tokenOut = (string) ($payload['token'] ?? $result['data']['token'] ?? $result['token'] ?? '');
$patientInfo = $this->tryAutoLinkByDirectMatch($abhaNum, $name, $mobile, $gender, $dob, $abhaMeta);
$responseBase = [
'ok' => 1,
'txn_id' => $txnIdOut,
'token' => $tokenOut,
'accounts' => $rawAccounts,
'card_base64' => $this->extractAbhaCardData($result),
'card_content_type' => $this->resolveAbhaCardContentType($result),
'card_source' => $this->resolveAbhaCardSource($result),
@ -427,6 +436,144 @@ class Abha extends BaseController
]);
}
// -------------------------------------------------------------------------
// Multi-Account Selection for Mobile OTP (ABDM M1)
// POST abha/find/mobile/select-account
// -------------------------------------------------------------------------
public function selectAccount()
{
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') ?? ''));
$token = trim((string) ($this->request->getPost('token') ?? ''));
$abhaNumber = trim((string) ($this->request->getPost('abha_number') ?? $this->request->getPost('abhaNumber') ?? ''));
$abhaAddress = trim((string) ($this->request->getPost('abha_address') ?? $this->request->getPost('abhaAddress') ?? ''));
$mobile = trim((string) ($this->request->getPost('mobile') ?? ''));
if ($txnId === '' && $token === '') {
return $this->response->setJSON(['ok' => 0, 'error_text' => 'txn_id and token are required']);
}
try {
$result = AbdmConnectorFactory::make()->abhaLoginSelectAccount([
'txnId' => $txnId,
'token' => $token,
'abha_number' => $abhaNumber,
'abha_address' => $abhaAddress,
]);
} catch (\Throwable $e) {
return $this->response->setStatusCode(500)->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]);
}
if (empty($result['ok']) || $result['ok'] != 1) {
return $this->response->setJSON([
'ok' => 0,
'error_text' => $this->extractBridgeErrorText($result, 'Account selection failed'),
'request_id' => (string) ($result['request_id'] ?? ''),
]);
}
$payload = $result;
if (is_array($result['data'] ?? null)) {
foreach ($result['data'] as $key => $value) {
if (! array_key_exists($key, $payload)) {
$payload[$key] = $value;
}
}
}
$profile = $this->pickGatewayAbhaProfile($payload);
$abhaNum = (string) ($profile['ABHANumber'] ?? $profile['abha_id'] ?? $payload['ABHANumber'] ?? $payload['abha_id'] ?? $abhaNumber);
$resAbhaAddress = (string) (
($profile['preferredAddress'] ?? '')
?: ($profile['preferredAbhaAddress'] ?? '')
?: ($profile['abha_address'] ?? '')
?: ($payload['preferredAddress'] ?? '')
?: ($payload['preferredAbhaAddress'] ?? '')
?: ($payload['abha_address'] ?? '')
?: $abhaAddress
);
$name = $this->extractAbhaProfileName($profile, $payload);
$photo = $this->extractAbhaProfilePhoto($profile, $payload);
$gender = (string) ($profile['gender'] ?? $payload['gender'] ?? '');
$dob = (string) ($profile['dob'] ?? $profile['date_of_birth'] ?? $payload['dob'] ?? $payload['date_of_birth'] ?? '');
$resMobile = (string) ($profile['mobile'] ?? $payload['mobile'] ?? $payload['mobileNumber'] ?? $mobile);
$verifiedStatus = (string) (($payload['gateway_abha_profile']['status'] ?? '') ?: ($profile['verifiedStatus'] ?? '') ?: ($profile['status'] ?? '') ?: ($payload['verifiedStatus'] ?? '') ?: ($payload['status'] ?? ''));
$verificationType = (string) (($payload['gateway_abha_profile']['abha_type'] ?? '') ?: ($profile['verificationType'] ?? '') ?: ($profile['abhaType'] ?? '') ?: ($payload['verificationType'] ?? '') ?: ($payload['abhaType'] ?? ''));
$kycVerified = $profile['kycVerified'] ?? $payload['kycVerified'] ?? null;
$mobileVerified = $profile['mobileVerified'] ?? $payload['mobileVerified'] ?? null;
$address = (string) (($profile['address'] ?? '') ?: ($profile['address_line'] ?? '') ?: ($payload['address'] ?? '') ?: ($payload['address_line'] ?? '') ?: ($payload['gateway_abha_profile']['address'] ?? '') ?: ($payload['gateway_patient']['address_line'] ?? ''));
$zip = (string) (($profile['pinCode'] ?? '') ?: ($profile['pin_code'] ?? '') ?: ($payload['pinCode'] ?? '') ?: ($payload['pin_code'] ?? '') ?: ($payload['gateway_abha_profile']['pin_code'] ?? '') ?: ($payload['gateway_patient']['pincode'] ?? ''));
$stateName = (string) (($profile['stateName'] ?? '') ?: ($profile['state_name'] ?? '') ?: ($payload['stateName'] ?? '') ?: ($payload['state_name'] ?? '') ?: ($payload['gateway_abha_profile']['state_name'] ?? '') ?: ($payload['gateway_patient']['state_name'] ?? ''));
$districtName = (string) (($profile['districtName'] ?? '') ?: ($profile['district_name'] ?? '') ?: ($payload['districtName'] ?? '') ?: ($payload['district_name'] ?? '') ?: ($payload['gateway_abha_profile']['district_name'] ?? '') ?: ($payload['gateway_patient']['district'] ?? ''));
$email = (string) (($profile['email'] ?? '') ?: ($payload['email'] ?? '') ?: ($payload['gateway_abha_profile']['email'] ?? '') ?: ($payload['gateway_patient']['email'] ?? ''));
$abhaMeta = [
'abha_address' => $resAbhaAddress,
'profile_photo' => $photo,
'verified_status' => $verifiedStatus,
'verification_type' => $verificationType,
'kyc_verified' => $kycVerified,
'mobile_verified' => $mobileVerified,
'address' => $address,
'district' => $districtName,
'state' => $stateName,
'zip' => $zip,
'email' => $email,
];
$patientInfo = $this->tryAutoLinkByDirectMatch($abhaNum, $name, $resMobile, $gender, $dob, $abhaMeta);
$responseBase = [
'ok' => 1,
'card_base64' => $this->extractAbhaCardData($result),
'card_content_type' => $this->resolveAbhaCardContentType($result),
'card_source' => $this->resolveAbhaCardSource($result),
'card_message' => $this->resolveAbhaCardMessage($result),
'abha_number' => $abhaNum,
'name' => $name,
'photo' => $photo,
'mobile' => $resMobile,
'gender' => $gender,
'dob' => $dob,
'abha_address' => $resAbhaAddress,
'verified_status' => $verifiedStatus,
'verification_type' => $verificationType,
'kyc_verified' => $kycVerified,
'mobile_verified' => $mobileVerified,
'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, $resMobile, $gender, $dob, '', $abhaField, $abhaNumClean);
return $this->response->setJSON($responseBase + [
'need_confirmation' => true,
'patient_id' => 0,
'p_code' => '',
'is_new_patient' => null,
'candidates' => $candidates,
]);
}
// -------------------------------------------------------------------------
// Step 4 — ABHA address suggestions / assignment
// POST abha/create/address_suggestions, POST abha/create/address

View File

@ -119,6 +119,14 @@ interface AbdmConnectorInterface
public function abhaEnrolMobileVerifyOtp(array $payload): array;
/**
* ABDM M1: Select a specific ABHA account when multiple accounts are linked to mobile.
* Calls gateway POST /api/v3/abha/login/select-account
*
* @param array<string, mixed> $payload Must contain 'txnId', 'token', 'abha_number', 'abha_address'
*/
public function abhaLoginSelectAccount(array $payload): array;
/**
* ABDM M1: List ABHA address suggestions for an in-progress enrolment.
*

View File

@ -167,6 +167,11 @@ class DirectAbdmConnector implements AbdmConnectorInterface
throw new \RuntimeException('DirectAbdmConnector::abhaEnrolMobileVerifyOtp() not implemented.');
}
public function abhaLoginSelectAccount(array $payload): array
{
throw new \RuntimeException('DirectAbdmConnector::abhaLoginSelectAccount() not implemented.');
}
public function abhaAddressSuggestions(array $payload): array
{
throw new \RuntimeException(

View File

@ -182,6 +182,11 @@ class DreamsoftConnector implements AbdmConnectorInterface
throw new \RuntimeException('ABHA enrolment mobile OTP verification requires the synchronous e-Atria Bridge connector.');
}
public function abhaLoginSelectAccount(array $payload): array
{
throw new \RuntimeException('ABHA account selection requires the synchronous e-Atria Bridge connector.');
}
public function abhaAddressSuggestions(array $payload): array
{
throw new \RuntimeException(

View File

@ -999,6 +999,26 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
return $this->attachOfficialAbhaCard($result);
}
public function abhaLoginSelectAccount(array $payload): array
{
$body = [
'txnId' => (string) ($payload['txnId'] ?? $payload['txn_id'] ?? ''),
'token' => (string) ($payload['token'] ?? ''),
'abha_number' => (string) ($payload['abha_number'] ?? $payload['abhaNumber'] ?? ''),
'abha_address' => (string) ($payload['abha_address'] ?? $payload['abhaAddress'] ?? ''),
];
if ($this->hfrId !== '' && empty($body['hfr_id'])) {
$body['hfr_id'] = $this->hfrId;
}
$result = $this->post('/v3/abha/login/select-account', $body);
if (empty($result['ok']) || (int) $result['ok'] !== 1) {
return $result;
}
return $this->attachOfficialAbhaCard($result);
}
public function abhaAddressSuggestions(array $payload): array
{
$txnId = (string) ($payload['txn_id'] ?? $payload['txnId'] ?? '');

View File

@ -7,6 +7,7 @@
*
* 1. Mobile number
* 2. OTP
* 2b. Account Selection (when multiple ABHA profiles are linked to mobile)
* 3. Verified profile + official ABHA card
*
* Handing the profile to the patient-match modal stays the caller's job.
@ -32,7 +33,44 @@
.abha-mobile-modal .abha-profile-list dd { margin:0; text-align:right; font-weight:600; overflow-wrap:anywhere; }
.abha-mobile-modal .abha-card-preview { min-height:230px; display:grid; place-items:center; border:1px solid #dce3ec; background:#f8fafc; }
.abha-mobile-modal .abha-card-preview img { max-width:100%; max-height:360px; object-fit:contain; }
@media (max-width:575.98px) { .abha-mobile-modal .modal-body{padding:16px}.abha-mobile-step:not(:last-child)::after{width:22px}.abha-mobile-modal .abha-profile-list>div{grid-template-columns:110px minmax(0,1fr)} }
/* Multi-Account Selection Card Styles */
.abha-account-card {
border: 1px solid #dce3ec;
border-radius: 10px;
padding: 14px 18px;
transition: all 0.2s ease-in-out;
cursor: pointer;
background: #ffffff;
}
.abha-account-card:hover {
border-color: #356cf4;
background: #f4f8ff;
box-shadow: 0 4px 12px rgba(53, 108, 244, 0.12);
transform: translateY(-1px);
}
.abha-account-card.selected {
border-color: #356cf4;
background: #edf4ff;
box-shadow: 0 0 0 2px #356cf4;
}
.abha-account-avatar {
width: 44px;
height: 44px;
border-radius: 50%;
background: #eef2f6;
display: grid;
place-items: center;
font-size: 1.25rem;
color: #356cf4;
flex-shrink: 0;
}
@media (max-width:575.98px) {
.abha-mobile-modal .modal-body{padding:16px}
.abha-mobile-step:not(:last-child)::after{width:22px}
.abha-mobile-modal .abha-profile-list>div{grid-template-columns:110px minmax(0,1fr)}
}
</style>
<div class="modal fade abha-mobile-modal" id="abhaMobileModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
@ -50,6 +88,7 @@
</div>
<div id="abhaMobileAlert"></div>
<!-- Step 1: Mobile Input -->
<section id="abhaMobileStep1">
<label class="form-label fw-semibold" for="abhaMobileNumber">Mobile Number</label>
<div class="input-group input-group-lg">
@ -60,6 +99,7 @@
<div class="form-text">Enter the mobile number registered with the patient's ABHA account to find and link it.</div>
</section>
<!-- Step 2: OTP Verification -->
<section id="abhaMobileStep2" class="d-none">
<p class="text-muted mb-2" id="abhaMobileOtpHint"></p>
<label class="form-label fw-semibold" for="abhaMobileOtp">6-digit OTP</label>
@ -76,6 +116,21 @@
</div>
</section>
<!-- Step 2b: Multi-Account Selection (When >1 account returned) -->
<section id="abhaMobileStepAccounts" class="d-none">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h6 class="mb-0 fw-bold text-dark"><i class="bi bi-people text-primary me-2"></i>Select ABHA Profile</h6>
<small class="text-muted" id="abhaAccountsCountHint">Multiple ABHA profiles found linked to this mobile number. Choose the patient's account:</small>
</div>
<button type="button" class="btn btn-outline-secondary btn-sm" id="abhaAccountsBackBtn">&larr; Back to OTP</button>
</div>
<div class="vstack gap-2" id="abhaAccountsList">
<!-- Populated by JavaScript -->
</div>
</section>
<!-- Step 3: Verified Profile & Card -->
<section id="abhaMobileStep3" class="d-none">
<div class="row g-4">
<div class="col-lg-6">
@ -100,7 +155,10 @@
<div class="abha-card-preview" id="abhaMobileCardWrap"><div class="text-center text-muted"><i class="bi bi-card-image fs-1 d-block mb-2"></i><span>Official ABHA card was not returned by the Bridge.</span></div></div>
</div>
</div>
<div class="d-flex justify-content-end mt-4">
<div class="d-flex justify-content-between align-items-center mt-4 gap-2 flex-wrap">
<div>
<button type="button" class="btn btn-outline-secondary d-none" id="abhaChangeAccountBtn">&larr; Choose Different ABHA Account</button>
</div>
<button type="button" class="btn btn-success" id="abhaMobileCompareBtn"><i class="bi bi-people me-1"></i>Compare with HMS Patients</button>
</div>
</section>
@ -115,8 +173,10 @@ window.AbhaMobileModal = (function () {
var modal;
var txnId = '';
var currentToken = '';
var verifiedMobile = '';
var verifiedProfile = null;
var linkedAccounts = [];
var onVerified = null;
var resendTimer = null;
var resendRemaining = 0;
@ -134,12 +194,20 @@ window.AbhaMobileModal = (function () {
$('#abhaMobileAlert').html(message ? '<div class="alert alert-' + type + ' py-2">' + message + '</div>' : '');
}
function showStep(step) {
$('#abhaMobileStep1,#abhaMobileStep2,#abhaMobileStep3').addClass('d-none');
$('#abhaMobileStep' + step).removeClass('d-none');
$('.abha-mobile-step').each(function () {
var itemStep = Number($(this).data('step'));
$(this).toggleClass('active', itemStep === step).toggleClass('done', itemStep < step);
});
$('#abhaMobileStep1,#abhaMobileStep2,#abhaMobileStepAccounts,#abhaMobileStep3').addClass('d-none');
if (step === 'accounts') {
$('#abhaMobileStepAccounts').removeClass('d-none');
$('.abha-mobile-step').each(function () {
var itemStep = Number($(this).data('step'));
$(this).toggleClass('active', itemStep === 2).toggleClass('done', itemStep < 2);
});
} else {
$('#abhaMobileStep' + step).removeClass('d-none');
$('.abha-mobile-step').each(function () {
var itemStep = Number($(this).data('step'));
$(this).toggleClass('active', itemStep === step).toggleClass('done', itemStep < step);
});
}
alertBox('', '');
}
function stopTimer() {
@ -180,6 +248,7 @@ window.AbhaMobileModal = (function () {
if (mobile.length !== 10) { alertBox('warning', 'Enter a valid 10-digit mobile number.'); return; }
verifiedMobile = mobile;
linkedAccounts = [];
var button = $('#abhaMobileSendOtpBtn').prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Sending');
$.post('<?= base_url('abha/find/mobile/request-otp') ?>', { mobile: mobile, find_abha: 1, '<?= csrf_token() ?>': csrf() }, function (response) {
button.prop('disabled', false).html('<i class="bi bi-send me-1"></i>Send OTP');
@ -205,13 +274,111 @@ window.AbhaMobileModal = (function () {
button.prop('disabled', false).html('<i class="bi bi-patch-check me-1"></i>Verify OTP');
if (!response || response.ok != 1) { alertBox('danger', apiMessage(response, 'OTP verification failed.')); return; }
stopTimer();
renderProfile(response);
txnId = response.txn_id || txnId;
currentToken = response.token || '';
linkedAccounts = Array.isArray(response.accounts) ? response.accounts : [];
if (linkedAccounts.length > 1) {
renderAccountsList(linkedAccounts, response);
} else {
renderProfile(response);
}
}, 'json').fail(function (xhr) {
button.prop('disabled', false).html('<i class="bi bi-patch-check me-1"></i>Verify OTP');
alertBox('danger', apiMessage(xhr.responseJSON, 'OTP verification failed.'));
});
}
function renderAccountsList(accounts, defaultResponse) {
$('#abhaAccountsCountHint').text('Found ' + accounts.length + ' ABHA accounts linked with mobile ' + maskMobile(verifiedMobile) + '. Select the patient:');
var container = $('#abhaAccountsList').empty();
accounts.forEach(function (acc, index) {
var name = acc.name || 'ABHA Account #' + (index + 1);
var abhaNum = acc.ABHANumber || acc.abhaNumber || acc.abha_id || '-';
var abhaAddr = acc.preferredAbhaAddress || acc.preferredAddress || acc.abhaAddress || acc.abha_address || '-';
var gender = genderText(acc.gender);
var dob = acc.dob || acc.date_of_birth || '-';
var status = acc.status || 'ACTIVE';
var iconClass = (gender === 'Female') ? 'bi-person-standing-dress text-danger' : 'bi-person-standing text-primary';
if (gender === 'Other') iconClass = 'bi-person text-info';
var photoHtml = acc.profilePhoto
? '<img src="' + (String(acc.profilePhoto).indexOf('data:') === 0 ? acc.profilePhoto : 'data:image/jpeg;base64,' + acc.profilePhoto) + '" class="rounded-circle" style="width:44px;height:44px;object-fit:cover">'
: '<div class="abha-account-avatar"><i class="bi ' + iconClass + '"></i></div>';
var cardHtml = $(
'<div class="abha-account-card d-flex align-items-center justify-content-between gap-3" data-index="' + index + '">' +
'<div class="d-flex align-items-center gap-3">' +
photoHtml +
'<div>' +
'<div class="d-flex align-items-center gap-2 flex-wrap">' +
'<h6 class="mb-0 fw-bold text-dark">' + escapeHtml(name) + '</h6>' +
'<span class="badge bg-primary-subtle text-primary border border-primary-subtle">' + escapeHtml(abhaAddr) + '</span>' +
(status === 'ACTIVE' ? '<span class="badge bg-success-subtle text-success">Active</span>' : '') +
'</div>' +
'<div class="text-muted small mt-1">' +
'<span class="me-3"><i class="bi bi-credit-card-2-front me-1"></i>ABHA: <strong>' + escapeHtml(formatAbha(abhaNum)) + '</strong></span>' +
'<span class="me-3"><i class="bi bi-gender-ambiguous me-1"></i>' + escapeHtml(gender) + '</span>' +
'<span><i class="bi bi-calendar-event me-1"></i>DOB: ' + escapeHtml(dob) + '</span>' +
'</div>' +
'</div>' +
'</div>' +
'<div>' +
'<button type="button" class="btn btn-sm btn-outline-primary select-acc-btn">' +
'<i class="bi bi-check2-circle me-1"></i>Select Account' +
'</button>' +
'</div>' +
'</div>'
);
cardHtml.on('click', function () {
selectAccount(acc, cardHtml);
});
container.append(cardHtml);
});
showStep('accounts');
}
function selectAccount(account, cardElement) {
if (cardElement) {
$('.abha-account-card').removeClass('selected');
cardElement.addClass('selected');
cardElement.find('.select-acc-btn').prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Loading');
}
var abhaNum = account.ABHANumber || account.abhaNumber || account.abha_id || '';
var abhaAddr = account.preferredAbhaAddress || account.preferredAddress || account.abhaAddress || account.abha_address || '';
$.post('<?= base_url('abha/find/mobile/select-account') ?>', {
txn_id: txnId,
token: currentToken,
abha_number: abhaNum,
abha_address: abhaAddr,
mobile: verifiedMobile,
find_abha: 1,
'<?= csrf_token() ?>': csrf()
}, function (response) {
if (cardElement) {
cardElement.find('.select-acc-btn').prop('disabled', false).html('<i class="bi bi-check2-circle me-1"></i>Select Account');
}
if (!response || response.ok != 1) {
alertBox('danger', apiMessage(response, 'Failed to select ABHA account.'));
return;
}
renderProfile(response);
}, 'json').fail(function (xhr) {
if (cardElement) {
cardElement.find('.select-acc-btn').prop('disabled', false).html('<i class="bi bi-check2-circle me-1"></i>Select Account');
}
alertBox('danger', apiMessage(xhr.responseJSON, 'Failed to select ABHA account.'));
});
}
function renderProfile(profile) {
verifiedProfile = profile;
$('#abhaMobileProfileName').text(profile.name || '-');
@ -237,6 +404,10 @@ window.AbhaMobileModal = (function () {
var reason = profile.card_message ? escapeHtml(profile.card_message) : 'ABDM did not issue an official ABHA card for this session.';
$('#abhaMobileCardWrap').html('<div class="text-center text-muted"><i class="bi bi-card-image fs-1 d-block mb-2"></i>' + reason + '</div>');
}
// Show "Choose Different Account" button if multiple accounts exist
$('#abhaChangeAccountBtn').toggleClass('d-none', !(linkedAccounts && linkedAccounts.length > 1));
showStep(3);
}
@ -247,6 +418,12 @@ window.AbhaMobileModal = (function () {
$('#abhaMobileResendBtn').on('click', sendOtp);
$('#abhaMobileVerifyBtn').on('click', verifyOtp);
$('#abhaMobileChangeBtn').on('click', function () { stopTimer(); showStep(1); $('#abhaMobileNumber').trigger('focus'); });
$('#abhaAccountsBackBtn').on('click', function () { showStep(2); $('#abhaMobileOtp').trigger('focus'); });
$('#abhaChangeAccountBtn').on('click', function () {
if (linkedAccounts && linkedAccounts.length > 1) {
showStep('accounts');
}
});
$('#abhaMobileNumber').on('keydown', function (event) { if (event.key === 'Enter') { event.preventDefault(); sendOtp(); } });
$('#abhaMobileOtp').on('keydown', function (event) { if (event.key === 'Enter') { event.preventDefault(); verifyOtp(); } });
@ -272,12 +449,15 @@ window.AbhaMobileModal = (function () {
open: function (prefillMobile, callback) {
onVerified = typeof prefillMobile === 'function' ? prefillMobile : callback;
txnId = '';
currentToken = '';
verifiedMobile = '';
verifiedProfile = null;
linkedAccounts = [];
stopTimer();
$('#abhaMobileNumber').val(typeof prefillMobile === 'string' ? prefillMobile : '');
$('#abhaMobileOtp').val('');
$('#abhaMobilePhoto').addClass('d-none');
$('#abhaChangeAccountBtn').addClass('d-none');
showStep(1);
modal.show();
window.setTimeout(function () { $('#abhaMobileNumber').trigger('focus'); }, 300);