Redesign ABHA Aadhaar creation flow
This commit is contained in:
parent
73ad7ef11f
commit
3e70ff8c4a
|
|
@ -773,6 +773,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/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']);
|
||||
$routes->get('abha/card/(:segment)', 'Abha::card/$1', ['filter' => 'permission:abdm.abha.create']);
|
||||
$routes->post('abha/register/validate', 'Abha::validateAbha', ['filter' => 'permission:abdm.abha.create']);
|
||||
$routes->post('abha/register/login/request-otp', 'Abha::loginRequestOtp', ['filter' => 'permission:abdm.abha.create']);
|
||||
|
|
|
|||
|
|
@ -102,9 +102,9 @@ class Abha extends BaseController
|
|||
);
|
||||
$name = $this->extractAbhaProfileName($profile, $payload);
|
||||
$photo = (string) ($profile['profilePhoto'] ?? $profile['profile_photo'] ?? $payload['profilePhoto'] ?? $payload['profile_photo'] ?? '');
|
||||
// Bridge/gateway verify-otp responses don't always echo the mobile back;
|
||||
// fall back to the mobile the user actually typed and OTP-verified.
|
||||
$mobile = (string) ($profile['mobile'] ?? $payload['mobile'] ?? $payload['mobileNumber'] ?? $requestMobile ?? '');
|
||||
// A different communication number is not verified until Step 3 succeeds.
|
||||
// Never persist the operator-entered number merely because Aadhaar OTP passed.
|
||||
$mobile = (string) ($profile['mobile'] ?? $payload['mobile'] ?? $payload['mobileNumber'] ?? '');
|
||||
$profileGender = (string) ($profile['gender'] ?? $payload['gender'] ?? '');
|
||||
$profileDob = (string) ($profile['dob'] ?? $profile['date_of_birth'] ?? $payload['dob'] ?? $payload['date_of_birth'] ?? '');
|
||||
$verifiedStatus = (string) (($payload['gateway_abha_profile']['status'] ?? '') ?: ($profile['verifiedStatus'] ?? '') ?: ($profile['status'] ?? '') ?: ($payload['verifiedStatus'] ?? '') ?: ($payload['status'] ?? ''));
|
||||
|
|
@ -142,6 +142,8 @@ class Abha extends BaseController
|
|||
'ok' => 1,
|
||||
'txn_id' => $newTxnId,
|
||||
'skip_mobile' => true,
|
||||
'card_base64' => $this->extractAbhaCardData($payload),
|
||||
'card_content_type' => $this->resolveAbhaCardContentType($payload),
|
||||
'abha_number' => $abhaNum,
|
||||
'name' => $name,
|
||||
'photo' => $photo,
|
||||
|
|
@ -303,6 +305,8 @@ class Abha extends BaseController
|
|||
|
||||
$responseBase = [
|
||||
'ok' => 1,
|
||||
'card_base64' => $this->extractAbhaCardData($payload),
|
||||
'card_content_type' => $this->resolveAbhaCardContentType($payload),
|
||||
'abha_number' => $abhaNum,
|
||||
'name' => $name,
|
||||
'photo' => $photo,
|
||||
|
|
@ -347,18 +351,93 @@ class Abha extends BaseController
|
|||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Step 4 — Save/finalise ABHA address
|
||||
// POST abha/create/address
|
||||
// Gateway has no address-assignment endpoint; return ok=1 as confirmation.
|
||||
// Step 4 — ABHA address suggestions / assignment
|
||||
// POST abha/create/address_suggestions, POST abha/create/address
|
||||
// -------------------------------------------------------------------------
|
||||
public function addressSuggestions()
|
||||
{
|
||||
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') ?? ''));
|
||||
if ($txnId === '') {
|
||||
return $this->response->setJSON(['ok' => 0, 'error_text' => 'txn_id is required']);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = AbdmConnectorFactory::make()->abhaAddressSuggestions(['txn_id' => $txnId]);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->response->setJSON(['ok' => 0, 'supported' => false, 'error_text' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (empty($result['ok']) || (int) $result['ok'] !== 1) {
|
||||
// A missing Bridge endpoint must not block ABHA creation — the modal
|
||||
// keeps the ABDM-assigned address and skips the selection step.
|
||||
$unsupported = in_array((int) ($result['http_code'] ?? 0), [404, 405], true);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => 0,
|
||||
'supported' => ! $unsupported,
|
||||
'error_text' => $unsupported
|
||||
? 'The ABDM Bridge does not expose ABHA address suggestions yet.'
|
||||
: $this->extractBridgeErrorText($result, 'Unable to load ABHA address suggestions.'),
|
||||
'request_id' => (string) ($result['request_id'] ?? ''),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => 1,
|
||||
'supported' => true,
|
||||
'txn_id' => $txnId,
|
||||
'suggestions' => array_values((array) ($result['suggestions'] ?? [])),
|
||||
]);
|
||||
}
|
||||
|
||||
public function address()
|
||||
{
|
||||
if (! $this->request->isAJAX()) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['ok' => 0, 'error_text' => 'Invalid request']);
|
||||
}
|
||||
|
||||
// Address assignment is noted for the staff. No gateway call available.
|
||||
return $this->response->setJSON(['ok' => 1, 'message' => 'ABHA created successfully.']);
|
||||
$txnId = trim((string) ($this->request->getPost('txn_id') ?? $this->request->getPost('txnId') ?? ''));
|
||||
$abhaAddress = trim((string) ($this->request->getPost('abha_address') ?? ''));
|
||||
|
||||
if ($abhaAddress === '') {
|
||||
return $this->response->setJSON(['ok' => 1, 'message' => 'ABHA created successfully.']);
|
||||
}
|
||||
|
||||
if (! preg_match('/^[a-zA-Z0-9._]{4,}$/', explode('@', $abhaAddress)[0])) {
|
||||
return $this->response->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => 'ABHA address must be at least 4 characters and may use only letters, numbers, dot or underscore.',
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = AbdmConnectorFactory::make()->abhaSetAddress([
|
||||
'txn_id' => $txnId,
|
||||
'abha_address' => $abhaAddress,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->response->setJSON(['ok' => 0, 'error_text' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (empty($result['ok']) || (int) $result['ok'] !== 1) {
|
||||
return $this->response->setJSON([
|
||||
'ok' => 0,
|
||||
'error_text' => $this->extractBridgeErrorText($result, 'Unable to set the ABHA address.'),
|
||||
'request_id' => (string) ($result['request_id'] ?? ''),
|
||||
]);
|
||||
}
|
||||
|
||||
$data = is_array($result['data'] ?? null) ? $result['data'] : [];
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => 1,
|
||||
'abha_address' => (string) ($result['abha_address'] ?? $data['abhaAddress'] ?? $data['abha_address'] ?? $abhaAddress),
|
||||
'message' => 'ABHA address updated successfully.',
|
||||
]);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -108,6 +108,20 @@ interface AbdmConnectorInterface
|
|||
*/
|
||||
public function abhaMobileVerifyOtp(array $payload): array;
|
||||
|
||||
/**
|
||||
* ABDM M1: List ABHA address suggestions for an in-progress enrolment.
|
||||
*
|
||||
* @param array<string, mixed> $payload Must contain 'txn_id'
|
||||
*/
|
||||
public function abhaAddressSuggestions(array $payload): array;
|
||||
|
||||
/**
|
||||
* ABDM M1: Set the chosen (suggested or custom) ABHA address.
|
||||
*
|
||||
* @param array<string, mixed> $payload Must contain 'txn_id' and 'abha_address'
|
||||
*/
|
||||
public function abhaSetAddress(array $payload): array;
|
||||
|
||||
/**
|
||||
* Fetch hospital facility details and official ABDM Scan & Share QR image.
|
||||
* Gateway response includes hospital_name, hfr_id, and qr_data data URI/base64.
|
||||
|
|
|
|||
|
|
@ -152,6 +152,20 @@ class DirectAbdmConnector implements AbdmConnectorInterface
|
|||
);
|
||||
}
|
||||
|
||||
public function abhaAddressSuggestions(array $payload): array
|
||||
{
|
||||
throw new \RuntimeException(
|
||||
'DirectAbdmConnector::abhaAddressSuggestions() not implemented.'
|
||||
);
|
||||
}
|
||||
|
||||
public function abhaSetAddress(array $payload): array
|
||||
{
|
||||
throw new \RuntimeException(
|
||||
'DirectAbdmConnector::abhaSetAddress() not implemented.'
|
||||
);
|
||||
}
|
||||
|
||||
public function facilityQrCode(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -167,6 +167,22 @@ class DreamsoftConnector implements AbdmConnectorInterface
|
|||
);
|
||||
}
|
||||
|
||||
public function abhaAddressSuggestions(array $payload): array
|
||||
{
|
||||
throw new \RuntimeException(
|
||||
'DreamsoftConnector does not support ABHA address selection. '
|
||||
. 'Set abdm.connector = eatria_bridge in .env to use M1 endpoints.'
|
||||
);
|
||||
}
|
||||
|
||||
public function abhaSetAddress(array $payload): array
|
||||
{
|
||||
throw new \RuntimeException(
|
||||
'DreamsoftConnector does not support ABHA address selection. '
|
||||
. 'Set abdm.connector = eatria_bridge in .env to use M1 endpoints.'
|
||||
);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// NHCX Claims
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -810,6 +810,109 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
return $this->post('/v3/abha/mobile/verify-otp', $body);
|
||||
}
|
||||
|
||||
public function abhaAddressSuggestions(array $payload): array
|
||||
{
|
||||
$txnId = (string) ($payload['txn_id'] ?? $payload['txnId'] ?? '');
|
||||
$body = ['txn_id' => $txnId, 'txnId' => $txnId];
|
||||
if ($this->hfrId !== '') {
|
||||
$body['hfr_id'] = $this->hfrId;
|
||||
}
|
||||
|
||||
$result = $this->postFirstAvailable([
|
||||
'/v3/abha/enrollment/suggestion',
|
||||
'/v3/abha/address/suggestion',
|
||||
'/v3/abha/enrol/suggestion',
|
||||
], $body);
|
||||
|
||||
if (empty($result['ok']) || (int) $result['ok'] !== 1) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result['suggestions'] = $this->normalizeAbhaAddressSuggestions($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function abhaSetAddress(array $payload): array
|
||||
{
|
||||
$txnId = (string) ($payload['txn_id'] ?? $payload['txnId'] ?? '');
|
||||
$address = (string) ($payload['abha_address'] ?? $payload['abhaAddress'] ?? '');
|
||||
$body = [
|
||||
'txn_id' => $txnId,
|
||||
'txnId' => $txnId,
|
||||
'abha_address' => $address,
|
||||
'abhaAddress' => $address,
|
||||
];
|
||||
if ($this->hfrId !== '') {
|
||||
$body['hfr_id'] = $this->hfrId;
|
||||
}
|
||||
|
||||
return $this->postFirstAvailable([
|
||||
'/v3/abha/enrollment/abha-address',
|
||||
'/v3/abha/address/set',
|
||||
'/v3/abha/enrol/abha-address',
|
||||
], $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bridge path naming for newer endpoints is still settling; try each
|
||||
* candidate and keep the first response that is not a 404/405.
|
||||
*
|
||||
* @param string[] $paths
|
||||
* @param array<string, mixed> $body
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function postFirstAvailable(array $paths, array $body): array
|
||||
{
|
||||
$result = ['ok' => 0, 'http_code' => 404, 'error_text' => 'Endpoint not available on the ABDM Bridge.'];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
$result = $this->post($path, $body);
|
||||
if (! in_array((int) ($result['http_code'] ?? 0), [404, 405], true)) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $result
|
||||
* @return string[]
|
||||
*/
|
||||
private function normalizeAbhaAddressSuggestions(array $result): array
|
||||
{
|
||||
$data = is_array($result['data'] ?? null) ? $result['data'] : [];
|
||||
|
||||
$raw = [];
|
||||
foreach ([
|
||||
$result['suggestions'] ?? null,
|
||||
$result['abhaAddressList'] ?? null,
|
||||
$result['abha_address_list'] ?? null,
|
||||
$data['suggestions'] ?? null,
|
||||
$data['abhaAddressList'] ?? null,
|
||||
$data['abha_address_list'] ?? null,
|
||||
] as $candidate) {
|
||||
if (is_array($candidate) && $candidate !== []) {
|
||||
$raw = $candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$addresses = [];
|
||||
foreach ($raw as $entry) {
|
||||
$value = is_array($entry)
|
||||
? (string) ($entry['abhaAddress'] ?? $entry['abha_address'] ?? $entry['address'] ?? $entry['value'] ?? '')
|
||||
: (string) $entry;
|
||||
$value = trim($value);
|
||||
if ($value !== '' && ! in_array($value, $addresses, true)) {
|
||||
$addresses[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $addresses;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Consent
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
</style>
|
||||
<?= view('partials/abha_patient_match_modal') ?>
|
||||
<?= view('partials/abha_verify_modal') ?>
|
||||
<?= view('partials/abha_create_modal') ?>
|
||||
<section class="content">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
|
@ -416,6 +417,45 @@
|
|||
<div class="tab-content pt-3" id="abhaSubTabsContent">
|
||||
<div class="tab-pane fade show active" id="abha-create" role="tabpanel" aria-labelledby="abha-create-tab">
|
||||
|
||||
<div class="row g-3 py-2" id="abha_launcher_cards">
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-primary">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<i class="bi bi-person-plus-fill fs-3 text-primary"></i>
|
||||
<h6 class="mb-0">Create ABHA</h6>
|
||||
</div>
|
||||
<p class="text-muted small mb-2">Register a new Ayushman Bharat Health Account for a patient using their Aadhaar number. OTP is sent to the Aadhaar-linked mobile.</p>
|
||||
<ul class="list-unstyled small text-muted mb-3">
|
||||
<li><i class="bi bi-check-circle-fill text-success me-1"></i>Aadhaar-based identity verification</li>
|
||||
<li><i class="bi bi-check-circle-fill text-success me-1"></i>Choose or customise the ABHA address</li>
|
||||
<li><i class="bi bi-check-circle-fill text-success me-1"></i>Official ABHA card stored for printing</li>
|
||||
</ul>
|
||||
<button type="button" class="btn btn-primary w-100" id="abha_open_create_modal_btn"><i class="bi bi-plus-lg me-1"></i>Create ABHA Record</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-success">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<i class="bi bi-shield-check fs-3 text-success"></i>
|
||||
<h6 class="mb-0">Verify ABHA Account</h6>
|
||||
</div>
|
||||
<p class="text-muted small mb-2">Verify an existing ABHA account using the ABHA address or ABHA number. An OTP is sent to the linked mobile for authentication.</p>
|
||||
<ul class="list-unstyled small text-muted mb-3">
|
||||
<li><i class="bi bi-check-circle-fill text-success me-1"></i>Verify via ABHA address or ABHA number</li>
|
||||
<li><i class="bi bi-check-circle-fill text-success me-1"></i>Link ABHA to a patient record</li>
|
||||
<li><i class="bi bi-check-circle-fill text-success me-1"></i>View and download the ABHA card</li>
|
||||
</ul>
|
||||
<button type="button" class="btn btn-success w-100" id="abha_open_verify_modal_btn"><i class="bi bi-shield-check me-1"></i>Verify ABHA Account</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Legacy inline wizard, superseded by AbhaCreateModal. -->
|
||||
<div class="d-none">
|
||||
<!-- Step progress indicator -->
|
||||
<div class="abha-progress-wrap mt-3 mb-4">
|
||||
<div class="abha-node active" id="abha_node_1">
|
||||
|
|
@ -515,6 +555,7 @@
|
|||
<div id="abha_created_result"></div>
|
||||
<div id="abha_step4_alert" class="mt-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -1401,6 +1442,20 @@
|
|||
});
|
||||
}
|
||||
|
||||
$(document).off('click.abhaLauncher').on('click.abhaLauncher', '#abha_open_create_modal_btn', function() {
|
||||
if (!window.AbhaCreateModal) {
|
||||
alert('ABHA create modal is unavailable. Reload the page and try again.');
|
||||
return;
|
||||
}
|
||||
window.AbhaCreateModal.open(function(profile) {
|
||||
window.AbhaPatientMatchModal.open(profile, profile.candidates || [], function(finalResponse) {
|
||||
if (typeof showRegResult === 'function') showRegResult(finalResponse);
|
||||
});
|
||||
});
|
||||
}).on('click.abhaLauncher', '#abha_open_verify_modal_btn', function() {
|
||||
openAbhaAccountVerification('', null);
|
||||
});
|
||||
|
||||
window.abhaRegReset = function() {
|
||||
regTxnId = null;
|
||||
stopQrScanner();
|
||||
|
|
|
|||
539
app/Views/partials/abha_create_modal.php
Normal file
539
app/Views/partials/abha_create_modal.php
Normal file
|
|
@ -0,0 +1,539 @@
|
|||
<?php
|
||||
/**
|
||||
* Partial: "Create ABHA" (ABDM M1 Aadhaar enrolment) modal.
|
||||
*
|
||||
* Mirrors partials/abha_verify_modal.php so both ABHA journeys behave the same:
|
||||
* 1. Aadhaar + communication mobile + consent
|
||||
* 2. Aadhaar OTP
|
||||
* 3. Communication-mobile OTP (only when it differs from the ABHA mobile)
|
||||
* 4. Verified profile + official ABHA card
|
||||
* 5. ABHA address selection (suggested or custom)
|
||||
*
|
||||
* Handing the profile to the patient-match modal stays the caller's job, exactly
|
||||
* like the verify flow — this modal never writes to patient_master by itself.
|
||||
*
|
||||
* Include once per page: <?= view('partials/abha_create_modal') ?>
|
||||
* Then call: window.AbhaCreateModal.open(function (profile) { ... });
|
||||
*/
|
||||
?>
|
||||
<style>
|
||||
.abha-create-modal .modal-content { border:0; border-radius:12px; overflow:hidden; }
|
||||
.abha-create-modal .modal-header { border-bottom:1px solid #e5eaf1; padding:18px 22px; }
|
||||
.abha-create-modal .modal-body { padding:22px; }
|
||||
.abha-create-steps { display:flex; justify-content:center; align-items:center; margin-bottom:24px; }
|
||||
.abha-create-step { display:flex; align-items:center; color:#8792a3; }
|
||||
.abha-create-step:not(:last-child)::after { content:""; width:44px; height:2px; background:#dce3ec; margin:0 7px; }
|
||||
.abha-create-step.done:not(:last-child)::after { background:#356cf4; }
|
||||
.abha-create-step span { width:30px; height:30px; display:grid; place-items:center; border-radius:50%; background:#eef2f6; font-weight:700; }
|
||||
.abha-create-step.active span { color:#1748ce; background:#fff; border:2px solid #356cf4; box-shadow:0 0 0 4px #edf2ff; }
|
||||
.abha-create-step.done span { color:#fff; background:#356cf4; }
|
||||
.abha-create-consent { max-height:190px; overflow-y:auto; border:1px dashed #d4dbe6; border-radius:8px; padding:12px; background:#fbfcfe; }
|
||||
.abha-create-address { display:block; border:1px solid #dce3ec; border-radius:8px; padding:11px 14px; cursor:pointer; font-family:ui-monospace,SFMono-Regular,Menlo,monospace; }
|
||||
.abha-create-address:has(input:checked) { border-color:#356cf4; background:#edf2ff; box-shadow:0 0 0 1px #356cf4; }
|
||||
.abha-profile-list { margin:0; }
|
||||
.abha-profile-list>div { display:grid; grid-template-columns:150px minmax(0,1fr); gap:14px; padding:10px 0; border-bottom:1px solid #edf0f4; }
|
||||
.abha-profile-list dt { color:#6b7688; font-weight:500; }
|
||||
.abha-profile-list dd { margin:0; text-align:right; font-weight:600; overflow-wrap:anywhere; }
|
||||
.abha-card-preview { min-height:230px; display:grid; place-items:center; border:1px solid #dce3ec; background:#f8fafc; }
|
||||
.abha-card-preview img { max-width:100%; max-height:360px; object-fit:contain; }
|
||||
@media (max-width:575.98px) { .abha-create-modal .modal-body{padding:16px}.abha-create-step:not(:last-child)::after{width:22px}.abha-profile-list>div{grid-template-columns:110px minmax(0,1fr)} }
|
||||
</style>
|
||||
|
||||
<div class="modal fade abha-create-modal" id="abhaCreateModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static">
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="bi bi-person-plus-fill text-primary me-2"></i>Create ABHA</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="abha-create-steps" aria-label="ABHA creation progress">
|
||||
<div class="abha-create-step active" data-step="1"><span>1</span></div>
|
||||
<div class="abha-create-step" data-step="2"><span>2</span></div>
|
||||
<div class="abha-create-step" data-step="3"><span>3</span></div>
|
||||
<div class="abha-create-step" data-step="4"><span>4</span></div>
|
||||
<div class="abha-create-step" data-step="5"><span>5</span></div>
|
||||
</div>
|
||||
<div id="abhaCreateAlert"></div>
|
||||
|
||||
<!-- Step 1: Aadhaar + communication mobile + consent -->
|
||||
<section id="abhaCreateStep1">
|
||||
<div class="text-center mb-3">
|
||||
<i class="bi bi-fingerprint text-primary" style="font-size:2.2rem"></i>
|
||||
<h6 class="mt-2 mb-1">Enter Aadhaar Details</h6>
|
||||
<div class="text-muted small">An OTP will be sent to the mobile number linked with this Aadhaar.</div>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold" for="abhaCreateAadhaar">Aadhaar Number <span class="text-danger">*</span></label>
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-text"><i class="bi bi-person-vcard"></i></span>
|
||||
<input type="password" class="form-control" id="abhaCreateAadhaar" maxlength="14" inputmode="numeric" autocomplete="off" placeholder="0000 0000 0000">
|
||||
<button class="btn btn-outline-secondary" type="button" id="abhaCreateAadhaarToggle" aria-label="Show Aadhaar"><i class="bi bi-eye"></i></button>
|
||||
</div>
|
||||
<div class="form-text"><i class="bi bi-lock-fill me-1"></i>Aadhaar is encrypted before transmission and never stored in HMS.</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold" for="abhaCreateMobile">Mobile Number (For ABHA Communication)</label>
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-text"><i class="bi bi-phone"></i></span>
|
||||
<input type="text" class="form-control" id="abhaCreateMobile" maxlength="10" inputmode="numeric" autocomplete="tel" placeholder="10-digit mobile">
|
||||
</div>
|
||||
<div class="form-text">If this differs from the Aadhaar-linked mobile, it is verified separately.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="abha-create-consent mt-3">
|
||||
<div class="fw-semibold mb-2 text-decoration-underline">Consent Language</div>
|
||||
<div class="small">I hereby declare that:</div>
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="abhaCreateConsent1">
|
||||
<label class="form-check-label small" for="abhaCreateConsent1">
|
||||
I am voluntarily sharing my Aadhaar Number / Virtual ID issued by the Unique Identification Authority of India ("UIDAI"), and my demographic information for the purpose of creating an Ayushman Bharat Health Account number ("ABHA number") and Ayushman Bharat Health Account address ("ABHA Address"). I authorize NHA to use my Aadhaar number / Virtual ID for performing Aadhaar based authentication with UIDAI as per the provisions of the Aadhaar (Targeted Delivery of Financial and other Subsidies, Benefits and Services) Act, 2016 for the aforesaid purpose.
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="abhaCreateConsent2">
|
||||
<label class="form-check-label small" for="abhaCreateConsent2">
|
||||
I consent to usage of my ABHA address and ABHA number for linking of my legacy (past) government health records and those which will be generated during this encounter.
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="abhaCreateConsent3">
|
||||
<label class="form-check-label small" for="abhaCreateConsent3">
|
||||
I authorize the sharing of all my health records with healthcare provider(s) for the purpose of providing healthcare services to me during this encounter.
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="abhaCreateConsent4">
|
||||
<label class="form-check-label small" for="abhaCreateConsent4">
|
||||
I confirm that I have duly informed and explained to the beneficiary the contents of the consent for the aforementioned purposes.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="abhaCreateSendOtpBtn"><i class="bi bi-send me-1"></i>Send OTP</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Step 2: Aadhaar OTP -->
|
||||
<section id="abhaCreateStep2" class="d-none">
|
||||
<div class="text-center mb-3">
|
||||
<i class="bi bi-chat-dots-fill text-success" style="font-size:2.2rem"></i>
|
||||
<h6 class="mt-2 mb-1">Enter OTP</h6>
|
||||
<div class="text-muted small" id="abhaCreateOtpHint">OTP sent to the Aadhaar registered mobile number.</div>
|
||||
</div>
|
||||
<label class="form-label fw-semibold" for="abhaCreateOtp">6-digit OTP</label>
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-text"><i class="bi bi-key"></i></span>
|
||||
<input type="text" class="form-control" id="abhaCreateOtp" maxlength="6" inputmode="numeric" autocomplete="one-time-code" placeholder="Enter OTP">
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mt-3 gap-2 flex-wrap">
|
||||
<div>
|
||||
<button type="button" class="btn btn-link px-0 me-3" id="abhaCreateResendBtn" disabled>Resend OTP in 60s</button>
|
||||
<button type="button" class="btn btn-link px-0 text-secondary" id="abhaCreateChangeAadhaarBtn">← Change Aadhaar</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-success" id="abhaCreateVerifyOtpBtn"><i class="bi bi-shield-check me-1"></i>Verify & Create ABHA</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Step 3: communication-mobile OTP -->
|
||||
<section id="abhaCreateStep3" class="d-none">
|
||||
<div class="text-center mb-3">
|
||||
<i class="bi bi-phone-fill text-primary" style="font-size:2.2rem"></i>
|
||||
<h6 class="mt-2 mb-1">Verify New Mobile Number</h6>
|
||||
<div class="text-muted small" id="abhaCreateMobileHint"></div>
|
||||
</div>
|
||||
<label class="form-label fw-semibold" for="abhaCreateMobileOtp">Mobile OTP</label>
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-text"><i class="bi bi-key"></i></span>
|
||||
<input type="text" class="form-control" id="abhaCreateMobileOtp" maxlength="6" inputmode="numeric" autocomplete="one-time-code" placeholder="6-digit OTP">
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mt-3 gap-2 flex-wrap">
|
||||
<button type="button" class="btn btn-link px-0" id="abhaCreateMobileResendBtn" disabled>Resend OTP in 60s</button>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-outline-secondary" id="abhaCreateChangeMobileBtn">Change Mobile</button>
|
||||
<button type="button" class="btn btn-outline-secondary" id="abhaCreateSkipMobileBtn">Use Aadhaar-linked Mobile</button>
|
||||
<button type="button" class="btn btn-primary" id="abhaCreateVerifyMobileBtn"><i class="bi bi-check2-circle me-1"></i>Verify & Update Mobile</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Step 4: profile + card -->
|
||||
<section id="abhaCreateStep4" class="d-none">
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-6">
|
||||
<div class="d-flex align-items-center gap-3 mb-3">
|
||||
<img id="abhaCreatePhoto" class="rounded d-none" alt="ABHA profile" style="width:76px;height:76px;object-fit:cover">
|
||||
<div>
|
||||
<span class="badge bg-success-subtle text-success mb-1"><i class="bi bi-patch-check-fill me-1"></i><span id="abhaCreateStatusText">ABHA Ready</span></span>
|
||||
<h4 class="mb-0" id="abhaCreateProfileName"></h4>
|
||||
<div class="text-muted" id="abhaCreateProfileAddress"></div>
|
||||
</div>
|
||||
</div>
|
||||
<dl class="abha-profile-list">
|
||||
<div><dt>ABHA Number</dt><dd id="abhaCreateProfileNumber">-</dd></div>
|
||||
<div><dt>ABHA Address</dt><dd id="abhaCreateProfileId">-</dd></div>
|
||||
<div><dt>Full Name</dt><dd id="abhaCreateProfileFullName">-</dd></div>
|
||||
<div><dt>Date of Birth</dt><dd id="abhaCreateProfileDob">-</dd></div>
|
||||
<div><dt>Gender</dt><dd id="abhaCreateProfileGender">-</dd></div>
|
||||
<div><dt>Mobile</dt><dd id="abhaCreateProfileMobile">-</dd></div>
|
||||
<div><dt>Address</dt><dd id="abhaCreateProfileFullAddress">-</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="abha-card-preview" id="abhaCreateCardWrap">
|
||||
<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>
|
||||
<a class="btn btn-outline-primary w-100 mt-2 d-none" id="abhaCreateDownloadCard" download="ABHA-card"><i class="bi bi-download me-1"></i>Download ABHA</a>
|
||||
<button type="button" class="btn btn-success w-100 mt-2" id="abhaCreateRegisterBtn"><i class="bi bi-person-check me-1"></i>Register Patient</button>
|
||||
<button type="button" class="btn btn-primary w-100 mt-2" id="abhaCreateChooseAddressBtn"><i class="bi bi-at me-1"></i>Create New ABHA Address</button>
|
||||
<div class="form-text mt-2"><i class="bi bi-info-circle me-1"></i>Click <strong>Register Patient</strong> to compare this ABHA with existing HMS records.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Step 5: ABHA address selection -->
|
||||
<section id="abhaCreateStep5" class="d-none">
|
||||
<div class="text-center mb-3">
|
||||
<i class="bi bi-briefcase-fill text-warning" style="font-size:2.2rem"></i>
|
||||
<h6 class="mt-2 mb-1">Choose Your ABHA Address</h6>
|
||||
<div class="text-muted small">Select a suggested ABHA address or enter a custom one. This is your digital health identity.</div>
|
||||
</div>
|
||||
<div id="abhaCreateAddressAlert"></div>
|
||||
<div id="abhaCreateAddressList" class="d-grid gap-2"></div>
|
||||
<div class="text-center mt-2">
|
||||
<button type="button" class="btn btn-link" id="abhaCreateCustomToggle">Use a custom address instead →</button>
|
||||
</div>
|
||||
<div id="abhaCreateCustomWrap" class="d-none">
|
||||
<label class="form-label fw-semibold" for="abhaCreateCustomAddress">Custom ABHA Address</label>
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-text"><i class="bi bi-at"></i></span>
|
||||
<input type="text" class="form-control" id="abhaCreateCustomAddress" placeholder="username" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-text">Minimum 4 characters. Letters, numbers, dot and underscore only.</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<button type="button" class="btn btn-outline-secondary" id="abhaCreateAddressBackBtn"><i class="bi bi-arrow-left me-1"></i>Back</button>
|
||||
<button type="button" class="btn btn-primary" id="abhaCreateConfirmAddressBtn"><i class="bi bi-briefcase me-1"></i>Confirm Address & Create ABHA</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.AbhaCreateModal = (function () {
|
||||
'use strict';
|
||||
|
||||
var modal;
|
||||
var onCompleted = null;
|
||||
var createTxnId = '';
|
||||
var mobileTxnId = '';
|
||||
var communicationMobile = '';
|
||||
var createdProfile = null;
|
||||
var resendTimer = null;
|
||||
var resendRemaining = 0;
|
||||
var mobileResendTimer = null;
|
||||
var mobileResendRemaining = 0;
|
||||
|
||||
function csrf() {
|
||||
var input = document.querySelector('input[name="<?= csrf_token() ?>"]');
|
||||
return input ? input.value : '<?= csrf_hash() ?>';
|
||||
}
|
||||
function escapeHtml(value) { return $('<div>').text(value == null ? '' : String(value)).html(); }
|
||||
function apiMessage(response, fallback) {
|
||||
return response && (response.error_text || response.message) ? escapeHtml(response.error_text || response.message) : fallback;
|
||||
}
|
||||
function alertBox(type, message) {
|
||||
$('#abhaCreateAlert').html(message ? '<div class="alert alert-' + type + ' py-2">' + message + '</div>' : '');
|
||||
}
|
||||
function showStep(step) {
|
||||
$('#abhaCreateStep1,#abhaCreateStep2,#abhaCreateStep3,#abhaCreateStep4,#abhaCreateStep5').addClass('d-none');
|
||||
$('#abhaCreateStep' + step).removeClass('d-none');
|
||||
$('.abha-create-step').each(function () {
|
||||
var itemStep = Number($(this).data('step'));
|
||||
$(this).toggleClass('active', itemStep === step).toggleClass('done', itemStep < step);
|
||||
});
|
||||
alertBox('', '');
|
||||
}
|
||||
function stopTimers() {
|
||||
if (resendTimer) window.clearInterval(resendTimer);
|
||||
if (mobileResendTimer) window.clearInterval(mobileResendTimer);
|
||||
resendTimer = null;
|
||||
mobileResendTimer = null;
|
||||
}
|
||||
function startResendTimer(button, seconds, setter, getter) {
|
||||
resendRemaining = Math.max(60, Number(seconds) || 60);
|
||||
button.prop('disabled', true);
|
||||
function tick() {
|
||||
if (resendRemaining <= 0) {
|
||||
window.clearInterval(resendTimer);
|
||||
resendTimer = null;
|
||||
button.prop('disabled', false).text('Resend OTP');
|
||||
return;
|
||||
}
|
||||
button.text('Resend OTP in ' + resendRemaining + 's');
|
||||
resendRemaining--;
|
||||
}
|
||||
if (resendTimer) window.clearInterval(resendTimer);
|
||||
tick();
|
||||
resendTimer = window.setInterval(tick, 1000);
|
||||
}
|
||||
function startMobileResendTimer(button) {
|
||||
mobileResendRemaining = 60;
|
||||
button.prop('disabled', true);
|
||||
function tick() {
|
||||
if (mobileResendRemaining <= 0) {
|
||||
window.clearInterval(mobileResendTimer);
|
||||
mobileResendTimer = null;
|
||||
button.prop('disabled', false).text('Resend OTP');
|
||||
return;
|
||||
}
|
||||
button.text('Resend OTP in ' + mobileResendRemaining + 's');
|
||||
mobileResendRemaining--;
|
||||
}
|
||||
if (mobileResendTimer) window.clearInterval(mobileResendTimer);
|
||||
tick();
|
||||
mobileResendTimer = window.setInterval(tick, 1000);
|
||||
}
|
||||
function digits(value) { return String(value || '').replace(/\D/g, ''); }
|
||||
function maskMobile(value) {
|
||||
var mobile = digits(value);
|
||||
return mobile.length === 10 ? '******' + mobile.slice(-4) : mobile;
|
||||
}
|
||||
function genderText(value) {
|
||||
var gender = String(value || '').toUpperCase();
|
||||
if (gender === 'M' || gender === '1' || gender === 'MALE') return 'Male';
|
||||
if (gender === 'F' || gender === '2' || gender === 'FEMALE') return 'Female';
|
||||
if (gender === 'O' || gender === '3' || gender === 'OTHER') return 'Other';
|
||||
return gender || '-';
|
||||
}
|
||||
function formatAbha(value) { return String(value || '').replace(/(\d{2})(\d{4})(\d{4})(\d{4})/, '$1-$2-$3-$4'); }
|
||||
function photoSrc(value) {
|
||||
if (!value) return '';
|
||||
return String(value).indexOf('data:') === 0 ? value : 'data:image/jpeg;base64,' + value;
|
||||
}
|
||||
|
||||
function sendAadhaarOtp() {
|
||||
var aadhaar = digits($('#abhaCreateAadhaar').val());
|
||||
var mobile = digits($('#abhaCreateMobile').val());
|
||||
if (aadhaar.length !== 12) { alertBox('warning', 'Enter a valid 12-digit Aadhaar number.'); return; }
|
||||
if (mobile && mobile.length !== 10) { alertBox('warning', 'Enter a valid 10-digit mobile number.'); return; }
|
||||
if (!$('#abhaCreateConsent1').is(':checked')) { alertBox('warning', 'Aadhaar authentication consent is mandatory before sending the OTP.'); return; }
|
||||
if (!$('#abhaCreateConsent4').is(':checked')) { alertBox('warning', 'Confirm that the consent was explained to the patient.'); return; }
|
||||
|
||||
communicationMobile = mobile;
|
||||
var button = $('#abhaCreateSendOtpBtn').prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Sending');
|
||||
$.post('<?= base_url('abha/create/initiate') ?>', { aadhaar: aadhaar, auth_type: 'aadhaar_otp', '<?= csrf_token() ?>': csrf() }, function (response) {
|
||||
button.prop('disabled', false).html('<i class="bi bi-send me-1"></i>Send OTP');
|
||||
if (!response || response.ok != 1) { alertBox('danger', apiMessage(response, 'Unable to send Aadhaar OTP.')); return; }
|
||||
createTxnId = response.txn_id || '';
|
||||
$('#abhaCreateOtp').val('');
|
||||
$('#abhaCreateOtpHint').text('OTP sent to the mobile number linked with this Aadhaar.');
|
||||
showStep(2);
|
||||
startResendTimer($('#abhaCreateResendBtn'), 60);
|
||||
$('#abhaCreateOtp').trigger('focus');
|
||||
}, 'json').fail(function (xhr) {
|
||||
button.prop('disabled', false).html('<i class="bi bi-send me-1"></i>Send OTP');
|
||||
alertBox('danger', apiMessage(xhr.responseJSON, 'Unable to send Aadhaar OTP.'));
|
||||
});
|
||||
}
|
||||
|
||||
function verifyAadhaarOtp() {
|
||||
var otp = digits($('#abhaCreateOtp').val());
|
||||
if (otp.length !== 6) { alertBox('warning', 'Enter the 6-digit OTP.'); return; }
|
||||
var button = $('#abhaCreateVerifyOtpBtn').prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Verifying');
|
||||
$.post('<?= base_url('abha/create/verify_otp') ?>', { txn_id: createTxnId, otp: otp, mobile: communicationMobile, '<?= csrf_token() ?>': csrf() }, function (response) {
|
||||
button.prop('disabled', false).html('<i class="bi bi-shield-check me-1"></i>Verify & Create ABHA');
|
||||
if (!response || response.ok != 1) {
|
||||
alertBox('danger', apiMessage(response, 'Invalid OTP - Please enter a valid OTP. Entered OTP is either expired or incorrect.'));
|
||||
return;
|
||||
}
|
||||
stopTimers();
|
||||
createdProfile = response;
|
||||
createTxnId = response.txn_id || createTxnId;
|
||||
var abhaMobile = digits(response.mobile);
|
||||
if (communicationMobile && abhaMobile && communicationMobile !== abhaMobile) {
|
||||
requestMobileOtp();
|
||||
return;
|
||||
}
|
||||
renderProfile(response);
|
||||
}, 'json').fail(function (xhr) {
|
||||
button.prop('disabled', false).html('<i class="bi bi-shield-check me-1"></i>Verify & Create ABHA');
|
||||
alertBox('danger', apiMessage(xhr.responseJSON, 'OTP verification failed.'));
|
||||
});
|
||||
}
|
||||
|
||||
function requestMobileOtp() {
|
||||
$('#abhaCreateMobileHint').html('Enter the OTP sent to <strong>' + escapeHtml(maskMobile(communicationMobile)) + '</strong> to update the mobile registered on this ABHA account.');
|
||||
$('#abhaCreateMobileOtp').val('');
|
||||
showStep(3);
|
||||
$.post('<?= base_url('abha/create/communication') ?>', { mobile: communicationMobile, txn_id: createTxnId, '<?= csrf_token() ?>': csrf() }, function (response) {
|
||||
if (!response || response.ok != 1) {
|
||||
alertBox('warning', apiMessage(response, 'Unable to send the mobile OTP. You can skip this step.'));
|
||||
return;
|
||||
}
|
||||
mobileTxnId = response.txn_id || createTxnId;
|
||||
startMobileResendTimer($('#abhaCreateMobileResendBtn'));
|
||||
$('#abhaCreateMobileOtp').trigger('focus');
|
||||
}, 'json').fail(function (xhr) {
|
||||
alertBox('warning', apiMessage(xhr.responseJSON, 'Unable to send the mobile OTP. You can skip this step.'));
|
||||
});
|
||||
}
|
||||
|
||||
function verifyMobileOtp() {
|
||||
var otp = digits($('#abhaCreateMobileOtp').val());
|
||||
if (otp.length !== 6) { alertBox('warning', 'Enter the 6-digit mobile OTP.'); return; }
|
||||
var button = $('#abhaCreateVerifyMobileBtn').prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Verifying');
|
||||
$.post('<?= base_url('abha/create/verify_comm_otp') ?>', { txn_id: mobileTxnId, otp: otp, mobile: communicationMobile, '<?= csrf_token() ?>': csrf() }, function (response) {
|
||||
button.prop('disabled', false).html('<i class="bi bi-check2-circle me-1"></i>Verify & Update Mobile');
|
||||
if (!response || response.ok != 1) { alertBox('danger', apiMessage(response, 'Mobile OTP verification failed.')); return; }
|
||||
stopTimers();
|
||||
createdProfile = $.extend({}, createdProfile, response);
|
||||
renderProfile(createdProfile);
|
||||
}, 'json').fail(function (xhr) {
|
||||
button.prop('disabled', false).html('<i class="bi bi-check2-circle me-1"></i>Verify & Update Mobile');
|
||||
alertBox('danger', apiMessage(xhr.responseJSON, 'Mobile OTP verification failed.'));
|
||||
});
|
||||
}
|
||||
|
||||
function renderProfile(profile) {
|
||||
createdProfile = profile;
|
||||
$('#abhaCreateProfileName').text(profile.name || '-');
|
||||
$('#abhaCreateProfileFullName').text(profile.name || '-');
|
||||
$('#abhaCreateProfileAddress').text(profile.abha_address || '-');
|
||||
$('#abhaCreateProfileNumber').text(formatAbha(profile.abha_number) || '-');
|
||||
$('#abhaCreateProfileId').text(profile.abha_address || '-');
|
||||
$('#abhaCreateProfileDob').text(profile.dob || '-');
|
||||
$('#abhaCreateProfileGender').text(genderText(profile.gender));
|
||||
$('#abhaCreateProfileMobile').text(maskMobile(profile.mobile) || '-');
|
||||
$('#abhaCreateProfileFullAddress').text([profile.address, profile.district, profile.state, profile.zip].filter(Boolean).join(', ') || '-');
|
||||
$('#abhaCreateStatusText').text(profile.abha_number ? 'ABHA Ready' : 'ABHA Verified');
|
||||
|
||||
var photo = photoSrc(profile.photo);
|
||||
$('#abhaCreatePhoto').toggleClass('d-none', !photo).attr('src', photo || '');
|
||||
|
||||
var card = profile.card_base64 || '';
|
||||
if (card) {
|
||||
var cardSrc = String(card).indexOf('data:') === 0 ? card : 'data:' + (profile.card_content_type || 'image/png') + ';base64,' + card;
|
||||
$('#abhaCreateCardWrap').html('<img src="' + cardSrc + '" alt="Official ABHA card">');
|
||||
$('#abhaCreateDownloadCard').attr('href', cardSrc).removeClass('d-none');
|
||||
} else {
|
||||
$('#abhaCreateCardWrap').html('<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>');
|
||||
$('#abhaCreateDownloadCard').addClass('d-none');
|
||||
}
|
||||
showStep(4);
|
||||
}
|
||||
|
||||
function loadAddressSuggestions() {
|
||||
showStep(5);
|
||||
$('#abhaCreateAddressAlert').empty();
|
||||
$('#abhaCreateAddressList').html('<div class="text-center text-muted py-3"><span class="spinner-border spinner-border-sm me-2"></span>Loading suggestions…</div>');
|
||||
$('#abhaCreateCustomWrap').addClass('d-none');
|
||||
$('#abhaCreateCustomAddress').val('');
|
||||
|
||||
$.post('<?= base_url('abha/create/address_suggestions') ?>', { txn_id: createTxnId, '<?= csrf_token() ?>': csrf() }, function (response) {
|
||||
if (!response || response.ok != 1 || !(response.suggestions || []).length) {
|
||||
$('#abhaCreateAddressList').empty();
|
||||
$('#abhaCreateCustomWrap').removeClass('d-none');
|
||||
$('#abhaCreateAddressAlert').html('<div class="alert alert-warning py-2">' + apiMessage(response, 'No ABHA address suggestions are available.') + ' You can type a custom address below.</div>');
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
(response.suggestions || []).forEach(function (address, index) {
|
||||
html += '<label class="abha-create-address"><input class="form-check-input me-2" type="radio" name="abhaCreateAddressPick" value="' + escapeHtml(address) + '"' + (index === 0 ? ' checked' : '') + '>' + escapeHtml(address) + '</label>';
|
||||
});
|
||||
$('#abhaCreateAddressList').html(html);
|
||||
}, 'json').fail(function (xhr) {
|
||||
$('#abhaCreateAddressList').empty();
|
||||
$('#abhaCreateCustomWrap').removeClass('d-none');
|
||||
$('#abhaCreateAddressAlert').html('<div class="alert alert-warning py-2">' + apiMessage(xhr.responseJSON, 'Unable to load ABHA address suggestions.') + ' You can type a custom address below.</div>');
|
||||
});
|
||||
}
|
||||
|
||||
function confirmAddress() {
|
||||
var custom = $.trim($('#abhaCreateCustomAddress').val());
|
||||
var selected = $('input[name="abhaCreateAddressPick"]:checked').val() || '';
|
||||
var address = $('#abhaCreateCustomWrap').hasClass('d-none') ? selected : (custom || selected);
|
||||
if (!address) { $('#abhaCreateAddressAlert').html('<div class="alert alert-warning py-2">Select a suggested address or enter a custom one.</div>'); return; }
|
||||
|
||||
var button = $('#abhaCreateConfirmAddressBtn').prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Saving');
|
||||
$.post('<?= base_url('abha/create/address') ?>', { txn_id: createTxnId, abha_address: address, '<?= csrf_token() ?>': csrf() }, function (response) {
|
||||
button.prop('disabled', false).html('<i class="bi bi-briefcase me-1"></i>Confirm Address & Create ABHA');
|
||||
if (!response || response.ok != 1) {
|
||||
$('#abhaCreateAddressAlert').html('<div class="alert alert-danger py-2">' + apiMessage(response, 'Unable to set the ABHA address.') + '</div>');
|
||||
return;
|
||||
}
|
||||
createdProfile.abha_address = response.abha_address || address;
|
||||
renderProfile(createdProfile);
|
||||
alertBox('success', 'ABHA address updated to <strong>' + escapeHtml(createdProfile.abha_address) + '</strong>.');
|
||||
}, 'json').fail(function (xhr) {
|
||||
button.prop('disabled', false).html('<i class="bi bi-briefcase me-1"></i>Confirm Address & Create ABHA');
|
||||
$('#abhaCreateAddressAlert').html('<div class="alert alert-danger py-2">' + apiMessage(xhr.responseJSON, 'Unable to set the ABHA address.') + '</div>');
|
||||
});
|
||||
}
|
||||
|
||||
$(function () {
|
||||
modal = new bootstrap.Modal(document.getElementById('abhaCreateModal'));
|
||||
|
||||
$('#abhaCreateAadhaarToggle').on('click', function () {
|
||||
var field = $('#abhaCreateAadhaar');
|
||||
var isHidden = field.attr('type') === 'password';
|
||||
field.attr('type', isHidden ? 'text' : 'password');
|
||||
$(this).find('i').attr('class', isHidden ? 'bi bi-eye-slash' : 'bi bi-eye');
|
||||
});
|
||||
$('#abhaCreateSendOtpBtn').on('click', sendAadhaarOtp);
|
||||
$('#abhaCreateResendBtn').on('click', sendAadhaarOtp);
|
||||
$('#abhaCreateChangeAadhaarBtn').on('click', function () { stopTimers(); showStep(1); });
|
||||
$('#abhaCreateVerifyOtpBtn').on('click', verifyAadhaarOtp);
|
||||
$('#abhaCreateOtp').on('keydown', function (event) { if (event.key === 'Enter') { event.preventDefault(); verifyAadhaarOtp(); } });
|
||||
$('#abhaCreateVerifyMobileBtn').on('click', verifyMobileOtp);
|
||||
$('#abhaCreateMobileResendBtn').on('click', requestMobileOtp);
|
||||
$('#abhaCreateSkipMobileBtn').on('click', function () { stopTimers(); renderProfile(createdProfile || {}); });
|
||||
$('#abhaCreateChangeMobileBtn').on('click', function () {
|
||||
stopTimers();
|
||||
showStep(1);
|
||||
alertBox('warning', 'Enter the mobile number already linked with this Aadhaar, then request a new Aadhaar OTP.');
|
||||
$('#abhaCreateMobile').trigger('focus');
|
||||
});
|
||||
$('#abhaCreateChooseAddressBtn').on('click', loadAddressSuggestions);
|
||||
$('#abhaCreateAddressBackBtn').on('click', function () { showStep(4); });
|
||||
$('#abhaCreateCustomToggle').on('click', function () {
|
||||
$('#abhaCreateCustomWrap').toggleClass('d-none');
|
||||
if (!$('#abhaCreateCustomWrap').hasClass('d-none')) $('#abhaCreateCustomAddress').trigger('focus');
|
||||
});
|
||||
$('#abhaCreateConfirmAddressBtn').on('click', confirmAddress);
|
||||
|
||||
$('#abhaCreateRegisterBtn').on('click', function () {
|
||||
if (!createdProfile) return;
|
||||
var profile = createdProfile;
|
||||
$('#abhaCreateModal').one('hidden.bs.modal', function () {
|
||||
if (typeof onCompleted === 'function') onCompleted(profile);
|
||||
});
|
||||
modal.hide();
|
||||
});
|
||||
|
||||
$('#abhaCreateModal').on('hidden.bs.modal', stopTimers);
|
||||
});
|
||||
|
||||
return {
|
||||
open: function (callback) {
|
||||
onCompleted = callback;
|
||||
createTxnId = '';
|
||||
mobileTxnId = '';
|
||||
communicationMobile = '';
|
||||
createdProfile = null;
|
||||
stopTimers();
|
||||
$('#abhaCreateAadhaar,#abhaCreateMobile,#abhaCreateOtp,#abhaCreateMobileOtp,#abhaCreateCustomAddress').val('');
|
||||
$('#abhaCreateConsent1,#abhaCreateConsent2,#abhaCreateConsent3,#abhaCreateConsent4').prop('checked', false);
|
||||
$('#abhaCreatePhoto,#abhaCreateDownloadCard').addClass('d-none');
|
||||
showStep(1);
|
||||
modal.show();
|
||||
}
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
Loading…
Reference in New Issue
Block a user