Implement ABDM M1 multi-ABHA account selection for Mobile OTP login flow
This commit is contained in:
parent
29ac1ffe66
commit
156d12adba
|
|
@ -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']);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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'] ?? '');
|
||||
|
|
|
|||
|
|
@ -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">← 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">← 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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user