"""Public consumer verification — no auth. Single-read via the QR snapshot. GET /verify/{code} is READ-ONLY (no scan logging) so renders, prefetches, bots and React Strict Mode double-mounts never inflate the scan count. The frontend logs a scan explicitly via POST /scans/log after the page mounts. Must return valid data in Active, Grace, and Archived subscription states; never 404 for a valid QR. Resolves the four states: genuine | unverified | not_found | recalled. """ from fastapi import APIRouter, Request from app.models import QRCode, Organization, ScanLog from app.models.documents import now from app.models.enums import QRStatus, VerificationState, SubscriptionStatus router = APIRouter(tags=["verify"]) def _device_from_ua(ua: str) -> str: ua = (ua or "").lower() if "android" in ua: return "Android" if "iphone" in ua or "ipad" in ua or "ios" in ua: return "iOS" if "mobile" in ua: return "Other" return "Desktop" def _resolve_state(qr: QRCode, product: dict) -> VerificationState: if product.get("recalled"): return VerificationState.recalled if qr.status in (QRStatus.disabled, QRStatus.inactive, QRStatus.expired): return VerificationState.unverified return VerificationState.genuine @router.get("/verify/{qr_code}") async def verify(qr_code: str): """Read-only. Does NOT log a scan.""" qr = await QRCode.find_one(QRCode.code == qr_code) if not qr: return {"state": VerificationState.not_found, "message": "This QR code is not registered with VerifyPack."} org = await Organization.get(qr.org_id) if org and org.subscription_status == SubscriptionStatus.deleted: return {"state": VerificationState.not_found, "message": "This product record is no longer available."} snap = qr.snapshot or {} product = snap.get("product", {}) state = _resolve_state(qr, product) return { "state": state, "qr_code": qr.code, "serial": qr.serial, "brand": snap.get("brand"), "product": product, "batch": snap.get("batch"), "trust_score": product.get("trust_score", 90), "subscription_state": org.subscription_status if org else None, "powered_by": "VerifyPack", } @router.post("/scans/log") async def log_scan(qr_code: str, request: Request): """Record a single scan. Called once by the consumer page after it mounts.""" qr = await QRCode.find_one(QRCode.code == qr_code) if not qr: return {"logged": False} ua = request.headers.get("user-agent", "") try: await ScanLog(org_id=qr.org_id, qr_id=str(qr.id), qr_code=qr.code, product_id=qr.product_id, device=_device_from_ua(ua), ip=request.client.host if request.client else None).insert() qr.scan_count += 1 qr.last_scan_at = now() await qr.save() except Exception: return {"logged": False} return {"logged": True, "scan_count": qr.scan_count}