import QRCode from 'qrcode'; import { BRANCH_QR_BASE_URL } from '../constants.js'; const DEFAULT_QR_SECRET = 'qms-branch-qr-v1'; function getQrSecret() { return import.meta.env.VITE_QR_TOKEN_SECRET || DEFAULT_QR_SECRET; } function toBase64Url(bytes) { let binary = ''; bytes.forEach((byte) => { binary += String.fromCharCode(byte); }); return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); } function fromBase64Url(token) { const padded = token.replace(/-/g, '+').replace(/_/g, '/'); const padLength = (4 - (padded.length % 4)) % 4; const base64 = padded + '='.repeat(padLength); const binary = atob(base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i += 1) { bytes[i] = binary.charCodeAt(i); } return bytes; } function xorBytes(bytes, secret) { const key = new TextEncoder().encode(secret); return bytes.map((byte, index) => byte ^ key[index % key.length]); } /** Opaque token so QR URLs don't expose tenant code / branch id. */ export function encodeBranchQrToken(tenantCode, branchId, secret = getQrSecret()) { const plain = `v1:${String(tenantCode).trim().toUpperCase()}:${Number(branchId)}`; const plainBytes = new TextEncoder().encode(plain); return toBase64Url(xorBytes(plainBytes, secret)); } export function decodeBranchQrToken(token, secret = getQrSecret()) { try { const plain = new TextDecoder().decode(xorBytes(fromBase64Url(token), secret)); const match = /^v1:([^:]+):(\d+)$/.exec(plain); if (!match) return null; return { tenantCode: match[1], branchId: Number(match[2]), }; } catch { return null; } } export function getBranchTicketUrl(tenantCode, branchId) { const base = BRANCH_QR_BASE_URL.replace(/\/$/, ''); const token = encodeBranchQrToken(tenantCode, branchId); return `${base}/q/${token}`; } export async function generateBranchQrDataUrl(tenantCode, branchId) { return QRCode.toDataURL(getBranchTicketUrl(tenantCode, branchId), { errorCorrectionLevel: 'M', margin: 2, width: 1024, color: { dark: '#000000', light: '#ffffff', }, }); } function slugify(value) { return String(value) .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-|-$/g, '') || 'branch'; } export function downloadDataUrl(dataUrl, filename) { const link = document.createElement('a'); link.href = dataUrl; link.download = filename; link.click(); } export async function downloadBranchQr({ tenantCode, branchId, branchName }) { const dataUrl = await generateBranchQrDataUrl(tenantCode, branchId); downloadDataUrl(dataUrl, `qr-${slugify(branchName)}-${branchId}.png`); return dataUrl; }