ABHA: fetch official card via bridge API and print base64 card

This commit is contained in:
DevSoftTech Server 2026-07-23 16:10:34 +05:30
parent 3bda6f5291
commit e89e186281
3 changed files with 805 additions and 61 deletions

View File

@ -725,6 +725,7 @@ $routes->post('abha/create/communication', 'Abha::communication');
$routes->post('abha/create/verify_comm_otp', 'Abha::verifyCommOtp');
$routes->post('abha/create/address', 'Abha::address');
$routes->get('abha/card/(:segment)', 'Abha::card/$1');
$routes->get('abha/card/official/(:segment)', 'Abha::officialCard/$1');
$routes->post('abha/register/validate', 'Abha::validateAbha');
$routes->get('abha/register/facility_qr', 'Abha::facilityQr');
$routes->get('AbdmTaskBoard', 'AbdmTaskBoard::index', ['filter' => $abdmPermFilter]);

View File

@ -372,14 +372,408 @@ class Abha extends BaseController
try { $dobLabel = (new \DateTime($dobRaw))->format('d M Y'); } catch (\Exception $e) { $dobLabel = $dobRaw; }
}
$hospitalBrand = $this->loadHospitalBranding();
$hmsId = trim((string) ($patient['p_code'] ?? ''));
if ($hmsId === '') {
$hmsId = 'PID-' . (string) ($patient['id'] ?? '');
}
$officialCardUrl = trim((string) ($hospitalBrand['official_card_url'] ?? ''));
if ($officialCardUrl === '') {
$officialCardUrl = 'https://abha.abdm.gov.in/abha/v3/';
}
$bridgePortalUrl = trim((string) ($hospitalBrand['bridge_portal_url'] ?? ''));
if ($bridgePortalUrl === '') {
$bridgePortalUrl = 'https://abdm-bridge.e-atria.in/';
}
$barcodeSvg = $this->buildCode39Svg($hmsId);
return view('abha/card', [
'patient' => $patient,
'abha_num' => $abhaDisp,
'abha_raw' => $abhaNumClean,
'patient_id' => (int) ($patient['id'] ?? 0),
'abha_address' => (string) ($patient['abha_address'] ?? ''),
'gender' => $genderLabel,
'dob' => $dobLabel,
'hospital_name' => (string) ($hospitalBrand['hospital_name'] ?? 'E-Atria Hospital'),
'hospital_logo_url' => (string) ($hospitalBrand['hospital_logo_url'] ?? base_url('assets/img/logo.png')),
'brand_name' => 'E-Atria',
'hms_id' => $hmsId,
'hms_barcode_svg' => $barcodeSvg,
'official_card_url' => $officialCardUrl,
'bridge_portal_url' => $bridgePortalUrl,
]);
}
// -------------------------------------------------------------------------
// ABHA Official Card fetch — GET abha/card/official/{abha_number}
// Proxies bridge GET /api/v3/abha/card and returns card_data for browser print.
// -------------------------------------------------------------------------
public function officialCard(string $abhaNumber = '')
{
$abhaNumClean = preg_replace('/\D/', '', $abhaNumber);
if (strlen($abhaNumClean) !== 14) {
return $this->response->setStatusCode(400)->setJSON([
'ok' => 0,
'error_text' => 'Invalid ABHA number.',
]);
}
$db = \Config\Database::connect();
$fields = $db->getFieldNames('patient_master') ?? [];
$abhaField = null;
foreach (['abha_id', 'abha_no', 'abha'] as $f) {
if (in_array($f, $fields, true)) {
$abhaField = $f;
break;
}
}
if ($abhaField === null && in_array('abha_address', $fields, true)) {
$abhaField = 'abha_address';
}
$patient = null;
if ($abhaField) {
$patient = $db->table('patient_master')->where($abhaField, $abhaNumClean)->get()->getRowArray();
if (! $patient) {
$patient = $db->table('patient_master')->where($abhaField, $abhaNumber)->get()->getRowArray();
}
}
if (! $patient) {
return $this->response->setStatusCode(404)->setJSON([
'ok' => 0,
'error_text' => 'Patient not found for this ABHA number.',
]);
}
$runtime = $this->resolveBridgeRuntimeSettings();
if (($runtime['ok'] ?? 0) !== 1) {
return $this->response->setStatusCode(500)->setJSON([
'ok' => 0,
'error_text' => (string) ($runtime['error_text'] ?? 'Bridge settings are incomplete.'),
]);
}
$query = [
'abha_number' => $abhaNumClean,
'patient_id' => (int) ($patient['id'] ?? 0),
'abha_address' => trim((string) ($patient['abha_address'] ?? '')),
'hfr_id' => (string) ($runtime['hfr_id'] ?? ''),
];
$result = $this->bridgeGet(
(string) ($runtime['url'] ?? ''),
(string) ($runtime['token'] ?? ''),
'/api/v3/abha/card',
$query
);
if (($result['ok'] ?? 0) !== 1) {
$httpCode = (int) ($result['http_code'] ?? 502);
if ($httpCode < 100 || $httpCode > 599) {
$httpCode = 502;
}
return $this->response->setStatusCode($httpCode)->setJSON([
'ok' => 0,
'error_text' => (string) ($result['error_text'] ?? 'Failed to fetch official ABHA card from bridge.'),
'bridge_http_code' => (int) ($result['http_code'] ?? 0),
]);
}
$payload = $result['data'] ?? $result;
$cardData = trim((string) ($payload['card_data'] ?? $payload['cardData'] ?? ''));
if ($cardData === '') {
return $this->response->setStatusCode(502)->setJSON([
'ok' => 0,
'error_text' => 'Bridge response does not contain card_data.',
]);
}
if (stripos($cardData, 'data:image') === 0) {
$parts = explode(',', $cardData, 2);
$cardData = $parts[1] ?? '';
}
return $this->response->setJSON([
'ok' => 1,
'card_data' => $cardData,
'abha_number' => $abhaNumClean,
'patient_id' => (int) ($patient['id'] ?? 0),
'request_id' => (string) ($result['request_id'] ?? ''),
]);
}
/**
* @return array<string, string>
*/
private function loadHospitalBranding(): array
{
$branding = [
'hospital_name' => '',
'hospital_logo_url' => base_url('assets/img/logo.png'),
'official_card_url' => '',
'bridge_portal_url' => '',
];
try {
$db = \Config\Database::connect();
if (! $db->tableExists('hospital_setting')) {
return $branding;
}
$rows = $db->table('hospital_setting')
->select('s_name, s_value')
->whereIn('s_name', [
'HOSPITAL_NAME',
'HOSPITAL_DISPLAY_NAME',
'HOSPITAL_TITLE',
'ABDM_OFFICIAL_CARD_URL',
'ABDM_BRIDGE_PORTAL_URL',
'EATRIA_BRIDGE_URL',
])
->orderBy('id', 'DESC')
->get()
->getResultArray();
$settings = [];
foreach ($rows as $row) {
$key = trim((string) ($row['s_name'] ?? ''));
if ($key === '' || array_key_exists($key, $settings)) {
continue;
}
$settings[$key] = trim((string) ($row['s_value'] ?? ''));
}
foreach (['HOSPITAL_NAME', 'HOSPITAL_DISPLAY_NAME', 'HOSPITAL_TITLE'] as $nameKey) {
if (! empty($settings[$nameKey])) {
$branding['hospital_name'] = (string) $settings[$nameKey];
break;
}
}
if (! empty($settings['ABDM_OFFICIAL_CARD_URL'])) {
$branding['official_card_url'] = (string) $settings['ABDM_OFFICIAL_CARD_URL'];
}
if (! empty($settings['ABDM_BRIDGE_PORTAL_URL'])) {
$branding['bridge_portal_url'] = (string) $settings['ABDM_BRIDGE_PORTAL_URL'];
} elseif (! empty($settings['EATRIA_BRIDGE_URL'])) {
$parsed = parse_url((string) $settings['EATRIA_BRIDGE_URL']);
$scheme = (string) ($parsed['scheme'] ?? 'https');
$host = (string) ($parsed['host'] ?? '');
if ($host !== '') {
$branding['bridge_portal_url'] = $scheme . '://' . $host . '/';
}
}
} catch (\Throwable $e) {
// Keep default branding if settings lookup fails.
}
if ($branding['hospital_name'] === '') {
$branding['hospital_name'] = 'E-Atria Hospital';
}
return $branding;
}
/**
* @return array<string,mixed>
*/
private function resolveBridgeRuntimeSettings(): array
{
$settings = [
'url' => rtrim((string) (getenv('EATRIA_BRIDGE_URL') ?: 'https://abdm-bridge.e-atria.in/api'), '/'),
'token' => trim((string) (getenv('EATRIA_BRIDGE_TOKEN') ?: '')),
'hfr_id' => trim((string) (getenv('ABDM_HFR_ID') ?: '')),
];
try {
$db = \Config\Database::connect();
if ($db->tableExists('hospital_setting')) {
$rows = $db->table('hospital_setting')
->select('s_name,s_value')
->whereIn('s_name', ['EATRIA_BRIDGE_URL', 'EATRIA_BRIDGE_TOKEN', 'ABDM_HFR_ID', 'H_HFR_ID'])
->get()
->getResultArray();
foreach ($rows as $row) {
$key = trim((string) ($row['s_name'] ?? ''));
$val = trim((string) ($row['s_value'] ?? ''));
if ($key === 'EATRIA_BRIDGE_URL' && $val !== '') {
$settings['url'] = rtrim($val, '/');
}
if ($key === 'EATRIA_BRIDGE_TOKEN' && $val !== '') {
$settings['token'] = $val;
}
if ($key === 'ABDM_HFR_ID' && $val !== '') {
$settings['hfr_id'] = $val;
}
if ($key === 'H_HFR_ID' && $val !== '' && (string) $settings['hfr_id'] === '') {
$settings['hfr_id'] = $val;
}
}
}
} catch (\Throwable $e) {
// Keep env/default values when DB lookup fails.
}
if (stripos((string) $settings['token'], 'Bearer ') === 0) {
$settings['token'] = trim(substr((string) $settings['token'], 7));
}
if ((string) $settings['url'] === '') {
return ['ok' => 0, 'error_text' => 'EATRIA_BRIDGE_URL is missing.'];
}
if ((string) $settings['token'] === '') {
return ['ok' => 0, 'error_text' => 'EATRIA_BRIDGE_TOKEN is missing.'];
}
if ((string) $settings['hfr_id'] === '') {
return ['ok' => 0, 'error_text' => 'ABDM_HFR_ID is missing.'];
}
return ['ok' => 1] + $settings;
}
/**
* @param array<string,mixed> $query
* @return array<string,mixed>
*/
private function bridgeGet(string $baseUrl, string $token, string $path, array $query = []): array
{
$base = rtrim($baseUrl, '/');
$normalizedPath = '/' . ltrim($path, '/');
$baseHasApiSuffix = (bool) preg_match('#/api$#i', $base);
$pathHasApiPrefix = str_starts_with($normalizedPath, '/api/');
if ($baseHasApiSuffix && $pathHasApiPrefix) {
$normalizedPath = substr($normalizedPath, 4);
}
$url = $base . $normalizedPath;
if ($query !== []) {
$url .= '?' . http_build_query($query);
}
try {
$client = service('curlrequest', [
'timeout' => 30,
'connect_timeout' => 10,
'http_errors' => false,
]);
$response = $client->get($url, [
'headers' => [
'Authorization' => 'Bearer ' . trim($token),
'Accept' => 'application/json',
],
]);
$httpCode = (int) $response->getStatusCode();
$decoded = json_decode((string) $response->getBody(), true);
if (! is_array($decoded)) {
return [
'ok' => 0,
'http_code' => $httpCode,
'error_text' => 'Non-JSON response from bridge.',
];
}
$ok = (int) ($decoded['ok'] ?? (($httpCode >= 200 && $httpCode < 300) ? 1 : 0));
if ($ok !== 1) {
return [
'ok' => 0,
'http_code' => $httpCode,
'error_text' => (string) ($decoded['error_text'] ?? $decoded['message'] ?? $decoded['error'] ?? 'Bridge request failed.'),
'data' => $decoded,
'request_id' => (string) ($decoded['request_id'] ?? ''),
];
}
return [
'ok' => 1,
'http_code' => $httpCode,
'data' => $decoded,
'request_id' => (string) ($decoded['request_id'] ?? ''),
];
} catch (\Throwable $e) {
return [
'ok' => 0,
'http_code' => 0,
'error_text' => $e->getMessage(),
];
}
}
private function buildCode39Svg(string $value): string
{
$normalized = strtoupper(trim($value));
if ($normalized === '') {
return '';
}
$supported = [
'0' => 'nnnwwnwnn', '1' => 'wnnwnnnnw', '2' => 'nnwwnnnnw', '3' => 'wnwwnnnnn',
'4' => 'nnnwwnnnw', '5' => 'wnnwwnnnn', '6' => 'nnwwwnnnn', '7' => 'nnnwnnwnw',
'8' => 'wnnwnnwnn', '9' => 'nnwwnnwnn', 'A' => 'wnnnnwnnw', 'B' => 'nnwnnwnnw',
'C' => 'wnwnnwnnn', 'D' => 'nnnnwwnnw', 'E' => 'wnnnwwnnn', 'F' => 'nnwnwwnnn',
'G' => 'nnnnnwwnw', 'H' => 'wnnnnwwnn', 'I' => 'nnwnnwwnn', 'J' => 'nnnnwwwnn',
'K' => 'wnnnnnnww', 'L' => 'nnwnnnnww', 'M' => 'wnwnnnnwn', 'N' => 'nnnnwnnww',
'O' => 'wnnnwnnwn', 'P' => 'nnwnwnnwn', 'Q' => 'nnnnnnwww', 'R' => 'wnnnnnwwn',
'S' => 'nnwnnnwwn', 'T' => 'nnnnwnwwn', 'U' => 'wwnnnnnnw', 'V' => 'nwwnnnnnw',
'W' => 'wwwnnnnnn', 'X' => 'nwnnwnnnw', 'Y' => 'wwnnwnnnn', 'Z' => 'nwwnwnnnn',
'-' => 'nwnnnnwnw', '.' => 'wwnnnnwnn', ' ' => 'nwwnnnwnn', '$' => 'nwnwnwnnn',
'/' => 'nwnwnnnwn', '+' => 'nwnnnwnwn', '%' => 'nnnwnwnwn', '*' => 'nwnnwnwnn',
];
$payload = '*' . $normalized . '*';
$clean = '';
$len = strlen($payload);
for ($i = 0; $i < $len; $i++) {
$ch = $payload[$i];
if (! isset($supported[$ch])) {
continue;
}
$clean .= $ch;
}
if ($clean === '') {
return '';
}
$narrow = 2;
$wide = 5;
$height = 56;
$quiet = 10;
$gap = 2;
$x = $quiet;
$rects = [];
$cleanLen = strlen($clean);
for ($i = 0; $i < $cleanLen; $i++) {
$pattern = $supported[$clean[$i]];
for ($j = 0; $j < 9; $j++) {
$w = ($pattern[$j] === 'w') ? $wide : $narrow;
if ($j % 2 === 0) {
$rects[] = '<rect x="' . $x . '" y="0" width="' . $w . '" height="' . $height . '" fill="#111" />';
}
$x += $w;
}
if ($i < ($cleanLen - 1)) {
$x += $gap;
}
}
$width = $x + $quiet;
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' . $width . ' ' . $height . '" role="img" aria-label="HMS Barcode">'
. implode('', $rects)
. '</svg>';
}
// -------------------------------------------------------------------------
// Private helper — find or auto-create patient from ABHA profile data
// -------------------------------------------------------------------------

View File

@ -6,69 +6,287 @@
<title>ABHA Card — <?= esc($patient['p_fname'] ?? '') ?></title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Segoe UI', Arial, sans-serif; background: #f0f4f8; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; padding: 32px 16px; }
body {
font-family: 'Segoe UI', Arial, sans-serif;
background: #f2f6fb;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding: 30px 14px;
color: #1f2937;
}
.abha-card {
width: 380px;
background: linear-gradient(135deg, #1a6fc4 0%, #0b3e8e 60%, #062a6e 100%);
border-radius: 18px;
padding: 24px 22px 20px;
width: 430px;
max-width: 100%;
background: #ffffff;
border: 1px solid #d7e3f3;
border-radius: 16px;
padding: 18px;
box-shadow: 0 8px 24px rgba(15, 40, 77, 0.12);
}
.top-strip {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
border-bottom: 1px solid #e4edf8;
padding-bottom: 12px;
margin-bottom: 14px;
}
.gov-brand {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.gov-emblem {
width: 38px;
height: 38px;
background: #0b4fa8;
color: #fff;
box-shadow: 0 8px 32px rgba(0,0,0,0.25);
position: relative;
overflow: hidden;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 13px;
flex-shrink: 0;
}
.abha-card::before {
content: '';
position: absolute;
top: -40px; right: -40px;
width: 160px; height: 160px;
background: rgba(255,255,255,0.07);
border-radius: 50%;
.gov-text {
font-size: 13px;
font-weight: 700;
color: #0c3d83;
line-height: 1.3;
}
.gov-text span {
display: block;
font-size: 11px;
color: #5b6b82;
font-weight: 500;
}
.eatria-brand {
display: flex;
align-items: center;
gap: 8px;
border: 1px solid #d6e3f6;
border-radius: 10px;
padding: 6px 9px;
background: #f8fbff;
flex-shrink: 0;
}
.eatria-brand img {
width: 24px;
height: 24px;
object-fit: contain;
}
.eatria-brand .title {
font-size: 11px;
font-weight: 800;
color: #114a92;
line-height: 1.15;
}
.eatria-brand .title span {
display: block;
color: #5f6f87;
font-size: 10px;
font-weight: 600;
}
.hospital-name {
margin-bottom: 12px;
background: #f3f8ff;
border: 1px solid #dce9f8;
border-radius: 10px;
padding: 8px 10px;
font-size: 12px;
color: #214268;
}
.hospital-name strong {
color: #0a3f84;
}
.card-body {
display: flex;
gap: 14px;
align-items: flex-start;
margin-bottom: 12px;
}
.card-header { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; }
.card-header img.abdm-logo { height: 36px; filter: brightness(0) invert(1); }
.card-header .brand { font-size: 14px; font-weight: 700; letter-spacing: .5px; line-height: 1.3; }
.card-header .brand span { display: block; font-size: 11px; font-weight: 400; opacity: .8; }
.card-body { display: flex; gap: 16px; align-items: flex-start; }
.patient-photo {
width: 72px; height: 72px; border-radius: 50%;
border: 3px solid rgba(255,255,255,0.7);
object-fit: cover; background: rgba(255,255,255,0.2);
width: 82px;
height: 82px;
border-radius: 50%;
border: 3px solid #d5e4f7;
object-fit: cover;
background: #ecf4ff;
flex-shrink: 0;
}
.patient-photo-placeholder {
width: 72px; height: 72px; border-radius: 50%;
border: 3px solid rgba(255,255,255,0.7);
background: rgba(255,255,255,0.2);
width: 82px;
height: 82px;
border-radius: 50%;
border: 3px solid #d5e4f7;
background: #ecf4ff;
display: flex; align-items: center; justify-content: center;
font-size: 30px; flex-shrink: 0;
font-size: 34px;
flex-shrink: 0;
color: #3465a7;
}
.patient-info { flex: 1; min-width: 0; }
.patient-name { font-size: 17px; font-weight: 700; text-transform: uppercase; margin-bottom: 4px; word-break: break-word; }
.patient-detail { font-size: 12px; opacity: .85; margin-bottom: 2px; }
.abha-number-block { margin-top: 18px; background: rgba(255,255,255,0.12); border-radius: 10px; padding: 10px 14px; }
.abha-label { font-size: 10px; text-transform: uppercase; letter-spacing: 1px; opacity: .75; margin-bottom: 3px; }
.abha-number { font-size: 22px; font-weight: 700; letter-spacing: 3px; word-spacing: 2px; }
.card-footer { margin-top: 14px; font-size: 10px; opacity: .6; text-align: right; }
.actions { margin-top: 24px; display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.btn-print {
background: #1a6fc4; color: #fff; border: none; border-radius: 8px;
padding: 10px 22px; font-size: 14px; cursor: pointer; font-weight: 600;
display: flex; align-items: center; gap: 6px;
.patient-name {
font-size: 21px;
font-weight: 800;
text-transform: uppercase;
color: #0f2f5f;
margin-bottom: 4px;
word-break: break-word;
}
.btn-print:hover { background: #0b3e8e; }
.patient-detail {
font-size: 13px;
color: #425771;
margin-bottom: 3px;
}
.abha-number-block {
margin-top: 6px;
background: #edf5ff;
border: 1px solid #d5e6fb;
border-radius: 10px;
padding: 10px 12px;
}
.abha-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1px;
color: #5f7391;
margin-bottom: 3px;
}
.abha-number {
font-size: 34px;
font-weight: 800;
letter-spacing: 3px;
color: #0a3d86;
line-height: 1.12;
word-break: break-word;
}
.barcode-box {
margin-top: 12px;
border: 1px solid #dbe7f6;
border-radius: 10px;
padding: 8px 10px;
background: #fff;
}
.barcode-caption {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1px;
color: #5f7391;
margin-bottom: 5px;
}
.barcode-visual {
width: 100%;
overflow: hidden;
}
.barcode-id {
margin-top: 5px;
font-size: 12px;
font-weight: 700;
color: #1e3f69;
text-align: center;
}
.card-footer {
margin-top: 10px;
font-size: 10px;
color: #637590;
text-align: right;
}
.actions {
margin-top: 22px;
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: center;
}
.btn-print {
background: #0b4ea8;
color: #fff;
border: none;
border-radius: 8px;
padding: 10px 18px;
font-size: 14px;
cursor: pointer;
font-weight: 700;
text-decoration: none;
}
.btn-print:hover { background: #083d84; }
.btn-abdm {
background: #fff; color: #1a6fc4; border: 2px solid #1a6fc4; border-radius: 8px;
padding: 9px 20px; font-size: 14px; cursor: pointer; font-weight: 600; text-decoration: none;
display: flex; align-items: center; gap: 6px;
background: #fff;
color: #0b4ea8;
border: 2px solid #0b4ea8;
border-radius: 8px;
padding: 9px 16px;
font-size: 14px;
cursor: pointer;
font-weight: 700;
text-decoration: none;
}
.btn-abdm:hover { background: #e8f0fe; }
.note { margin-top: 14px; font-size: 12px; color: #666; max-width: 380px; text-align: center; line-height: 1.5; }
.bridge-help-btn {
background: #f6fbff;
color: #0c4b95;
border: 1px solid #bdd4f2;
border-radius: 8px;
padding: 10px 14px;
font-size: 13px;
cursor: pointer;
font-weight: 700;
}
.bridge-help-btn:hover { background: #eaf4ff; }
.note {
margin-top: 14px;
font-size: 12px;
color: #4f5f75;
max-width: 430px;
text-align: center;
line-height: 1.6;
}
.bridge-guide {
max-width: 430px;
margin-top: 14px;
background: #ffffff;
border: 1px solid #d7e6f8;
border-radius: 12px;
padding: 14px;
font-size: 13px;
line-height: 1.55;
color: #304760;
}
.bridge-guide h3 {
margin-bottom: 8px;
font-size: 14px;
color: #0a3f84;
}
.bridge-guide ol {
margin-left: 18px;
}
.bridge-guide li {
margin-bottom: 6px;
}
.bridge-links {
margin-top: 10px;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.bridge-links a {
color: #0a4a96;
text-decoration: none;
font-weight: 700;
}
.bridge-links a:hover { text-decoration: underline; }
@media print {
body { background: #fff; padding: 0; }
.abha-card { box-shadow: none; }
.actions, .note { display: none; }
.actions, .note, .bridge-guide { display: none; }
}
</style>
</head>
@ -80,18 +298,33 @@ $abhaNumDisp = esc($abha_num ?? '');
$photoData = !empty($patient['abha_photo']) ? $patient['abha_photo'] : '';
$genderDisp = esc($gender ?? '');
$dobDisp = esc($dob ?? '');
$mphone = esc($patient['mphone1'] ?? '');
$pCode = esc($patient['p_code'] ?? '');
$abhaNumRaw = preg_replace('/\D/', '', $abha_num ?? '');
$hospitalName = esc($hospital_name ?? 'E-Atria Hospital');
$hospitalLogo = esc($hospital_logo_url ?? base_url('assets/img/logo.png'));
$brandName = esc($brand_name ?? 'E-Atria');
$hmsId = esc($hms_id ?? '');
$barcodeSvg = (string) ($hms_barcode_svg ?? '');
$abhaRaw = esc($abha_raw ?? '');
$officialCardUrl = esc($official_card_url ?? 'https://abha.abdm.gov.in/abha/v3/');
$bridgePortalUrl = esc($bridge_portal_url ?? 'https://abdm-bridge.e-atria.in/');
$officialCardApiUrl = base_url('abha/card/official/' . ($abhaRaw !== '' ? $abhaRaw : preg_replace('/\D/', '', (string) ($abha_num ?? ''))));
?>
<div class="abha-card">
<div class="card-header">
<!-- ABDM / NHA branding text instead of logo image -->
<div style="width:40px;height:40px;background:rgba(255,255,255,0.9);border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:900;color:#0b3e8e;font-size:13px;letter-spacing:-.5px;flex-shrink:0;">NHA</div>
<div class="brand">Ayushman Bharat Digital Mission
<span>ABHA — Health Account Card</span>
<div class="top-strip">
<div class="gov-brand">
<div class="gov-emblem">NHA</div>
<div class="gov-text">Ayushman Bharat Digital Mission
<span>ABHA Health Account Card</span>
</div>
</div>
<div class="eatria-brand">
<img src="<?= $hospitalLogo ?>" alt="<?= $brandName ?> Logo">
<div class="title"><?= $brandName ?><span>Digital Health Network</span></div>
</div>
</div>
<div class="hospital-name">
Hospital: <strong><?= $hospitalName ?></strong>
</div>
<div class="card-body">
@ -111,8 +344,8 @@ $abhaNumRaw = preg_replace('/\D/', '', $abha_num ?? '');
<?php if ($dobDisp): ?>
<div class="patient-detail">DOB: <?= $dobDisp ?></div>
<?php endif; ?>
<?php if ($pCode): ?>
<div class="patient-detail" style="margin-top:4px;opacity:.7;">HMS ID: <?= $pCode ?></div>
<?php if ($hmsId): ?>
<div class="patient-detail" style="margin-top:4px;">HMS ID: <?= $hmsId ?></div>
<?php endif; ?>
</div>
</div>
@ -122,21 +355,137 @@ $abhaNumRaw = preg_replace('/\D/', '', $abha_num ?? '');
<div class="abha-number"><?= $abhaNumDisp ?: '—' ?></div>
</div>
<div class="card-footer">Government of India &nbsp;|&nbsp; Ministry of Health &amp; Family Welfare</div>
<?php if ($barcodeSvg !== '' && $hmsId !== ''): ?>
<div class="barcode-box">
<div class="barcode-caption">HMS ID Barcode</div>
<div class="barcode-visual"><?= $barcodeSvg ?></div>
<div class="barcode-id"><?= $hmsId ?></div>
</div>
<?php endif; ?>
<div class="card-footer">Government of India | Ministry of Health &amp; Family Welfare</div>
</div>
<div class="actions">
<button class="btn-print" onclick="window.print()">🖨 Print / Save PDF</button>
<a class="btn-abdm" href="https://abha.abdm.gov.in/abha/v3/" target="_blank" rel="noopener noreferrer">
⬇ Download Official Card
<button class="btn-print" onclick="window.print()">Print / Save PDF</button>
<button type="button" class="btn-abdm" id="downloadOfficialCardBtn" data-fetch-url="<?= esc($officialCardApiUrl) ?>">
Download Official Card
</button>
<a class="bridge-help-btn" href="<?= $bridgePortalUrl ?>" target="_blank" rel="noopener noreferrer">
Open ABDM Bridge Portal
</a>
</div>
<p class="note" id="officialCardStatus" style="display:none;"></p>
<p class="note">
<strong>To download the official ABDM-issued ABHA card</strong> (with QR code),
click "Download Official Card" and log in with mobile number linked to ABHA&nbsp;
<strong><?= $abhaNumDisp ?></strong>.
This is hospital-branded ABHA print card. For QR-enabled official card, use the ABDM official flow below.
</p>
<div class="bridge-guide">
<h3>Official Card Download and Print (via ABDM Bridge assisted flow)</h3>
<ol>
<li>Click <strong>Download Official Card</strong> to fetch the official PNG card via ABDM Bridge API.</li>
<li>A print window opens automatically with the fetched official card image.</li>
<li>If bridge session/auth is invalid, open <strong>ABDM Bridge Portal</strong> and complete login/configuration.</li>
<li>Retry <strong>Download Official Card</strong> after bridge auth is active.</li>
</ol>
<div class="bridge-links">
<a href="<?= $bridgePortalUrl ?>" target="_blank" rel="noopener noreferrer">ABDM Bridge Portal</a>
<a href="<?= $officialCardUrl ?>" target="_blank" rel="noopener noreferrer">Official ABDM Card Website</a>
</div>
<p style="margin-top:8px;">
Official card will be downloaded for ABHA
<strong><?= $abhaNumDisp ?></strong>.
</p>
</div>
<script>
(function() {
var btn = document.getElementById('downloadOfficialCardBtn');
var statusEl = document.getElementById('officialCardStatus');
if (!btn || !statusEl) {
return;
}
function setStatus(msg, isError) {
statusEl.style.display = 'block';
statusEl.style.color = isError ? '#9f1239' : '#14532d';
statusEl.style.background = isError ? '#ffe4e6' : '#dcfce7';
statusEl.style.border = '1px solid ' + (isError ? '#fecdd3' : '#bbf7d0');
statusEl.style.borderRadius = '8px';
statusEl.style.padding = '8px 10px';
statusEl.textContent = msg;
}
function openPrintImage(base64Png) {
var imageSrc = 'data:image/png;base64,' + base64Png;
var w = window.open('', '_blank');
if (!w) {
setStatus('Popup blocked by browser. Please allow popups and try again.', true);
return;
}
var html = ''
+ '<!DOCTYPE html><html><head><title>Official ABHA Card</title>'
+ '<meta name="viewport" content="width=device-width, initial-scale=1" />'
+ '<style>body{margin:0;padding:18px;font-family:Arial,sans-serif;background:#fff;text-align:center;}'
+ 'img{max-width:100%;height:auto;border:1px solid #d4d4d8;box-shadow:0 4px 14px rgba(0,0,0,.08);}'
+ '.hint{margin-top:12px;color:#475569;font-size:12px;}</style></head><body>'
+ '<img src="' + imageSrc + '" alt="Official ABHA Card" />'
+ '<div class="hint">Use browser print to save or print the official card.</div>'
+ '<script>window.onload=function(){window.focus();window.print();};<\/script>'
+ '</body></html>';
w.document.open();
w.document.write(html);
w.document.close();
}
btn.addEventListener('click', function() {
var url = btn.getAttribute('data-fetch-url') || '';
if (!url) {
setStatus('Official card endpoint missing. Please contact admin.', true);
return;
}
btn.disabled = true;
btn.textContent = 'Fetching...';
setStatus('Fetching official card from ABDM Bridge...', false);
fetch(url, {
method: 'GET',
credentials: 'same-origin',
headers: {
'Accept': 'application/json'
}
})
.then(function(res) {
return res.json().then(function(body) {
return { ok: res.ok, body: body };
});
})
.then(function(result) {
var body = result.body || {};
if (!result.ok || Number(body.ok || 0) !== 1 || !body.card_data) {
var err = body.error_text || 'Unable to fetch official card from bridge.';
setStatus(err, true);
return;
}
setStatus('Official card fetched. Opening print window...', false);
openPrintImage(String(body.card_data));
})
.catch(function() {
setStatus('Network/server error while fetching official card.', true);
})
.finally(function() {
btn.disabled = false;
btn.textContent = 'Download Official Card';
});
});
})();
</script>
</body>
</html>