Cambodia certificate QR + Verification

How LUCT Cambodia CMS Registry generates certificate QR codes via the external Limkokwing QR SaaS, and how public scans land on the Verification site. Evidence from Vara’s CMS pack (7 Oct 2026) and QR / Verification Drive packs (8 Oct 2026). M5 campus integration — not Lesotho M4 Applicant Demo letters.

CMS→QR call site QR System inventoried Verification inventoried 685 KHM-PNH_ in dump GetStudentCertInfo
Canonical Markdown. cambodia-qr-system.md · Cambodia CMS hub: old-cms-cambodia/ · Integrations snippet: #integrations · Standup context: Lesotho CMS vs Applicant.
Secrets stay out of Pages. PHP trees, DB dumps, AES key material, night-script credentials, and live certificate payloads are not published. This page documents hosts, API shape, short-code rules, and Raisd implications only.

1. Summary

Certificate QR is not generated inside Portal API or the Applicant Demo. Cambodia Registry PHP calls an external QR registry; the QR payload is a URL into the public Verification app. Vara (8 Oct): “Do check in CMS Cambodia, there's a call to the QR system to generate the QR code.”

Three systems

SystemRoleHost / path
Cambodia CMS (Registry) Builds short_code + verify URL; POSTs create API; saves PNG cambodia/registry/qrcode_gen*.php (campus SoR)
QR System (SaaS) Registers QR identity; returns base64 PNG https://qr.limkokwing.net/api/create_qrcode_api
Verification site Public scan target; shows cert validity for campus path https://verification.limkokwing.net/cambodia/{token}

Standup actions (7–8 Oct 2026)

OwnerActionStatus
Mohd. Paramasvara Send Cambodia QR-system code Done — Drive QR System + Verification (WhatsApp 8 Oct)
Iman Suherman Review QR system → update CMS documentation Done (CMS→QR) — this page + Markdown; QR/Verification DB inventory when local

2. Runtime flow

Observed from current-style qrcode_gen.php (Mar 2026). Inputs arrive as query params: StdProgramID, StudentID, ProgramCode.

Issue (Registry) then scan (public)

Create stores the Verification URL in the QR DB, but the PNG itself encodes the short SaaS URL. Scans hit QR → HTTP redirect → Verification.

sequenceDiagram
  actor Staff as Registry staff
  participant REG as qrcode_gen.php
  participant QR as qr.limkokwing.net CI4
  participant VER as verification.limkokwing.net
  participant CMS as campus2_cambodia
  participant FILE as StudentID_ProgramCode.png

  Staff->>REG: StdProgramID · StudentID · ProgramCode
  REG->>REG: short_code = KHM-PNH_ + StdProgramID
  REG->>REG: token = AES-256-CBC(StdProgramID)
  REG->>QR: POST /api/create_qrcode_api
  Note over REG,QR: name · category=LUCT · short_code
url = verification…/cambodia/token QR->>QR: Insert qrcode.slug + url QR->>QR: PNG data = qr…/qrcode/short_code QR-->>REG: JSON Qrcode base64 PNG REG->>FILE: download PNG for certificate print actor Public as Scanner Public->>QR: GET /qrcode/KHM-PNH_… QR-->>Public: 302 → verification…/cambodia/token Public->>VER: GET /cambodia/token VER->>VER: AES decrypt → StdProgramID VER->>CMS: CALL GetStudentCertInfo(StdProgramID) CMS-->>VER: name · programme · CertSN · blob VER-->>Public: Verified HTML page

Issue checklist (Registry)

  1. Build short_code = KHM-PNH_{StdProgramID}.
  2. Encrypt StdProgramID → base64 token; build Verification URL under /cambodia/.
  3. POST create API (see §3). SaaS stores url; PNG encodes short redirect URL.
  4. Parse JSON field Qrcode (base64 PNG); save as {StudentID}_{ProgramCode}.png.

2b. Manual certificate ops (not in CMS code)

Standup 8 Oct (Dexter): Cambodia ↔ Cyberjaya still exchanges printed certificates / QR packs / student data outside qrcode_gen*. Do not invent a Portal API courier method. Automated create/scan remains M5; this swimlane is ops documentation only.

Cambodia ↔ Cyberjaya handoff

sequenceDiagram
  participant KH as Cambodia campus
  participant CY as Cyberjaya ops
  Note over KH,CY: Manual — not modelled in Registry PHP
  KH->>CY: Certificates · QR PNGs · student data pack
  CY->>CY: Print / stamp / process
  CY-->>KH: Return physical certificates
  Note over KH: Public scan still Verification → GetStudentCertInfo
          

Combined standup write-up (Applicant merge + fee SPs): fee-payment-sps.html · Markdown applicant-qr-fee-mechanisms.md.

3. Create API contract

Endpoint

ItemValue
MethodPOST
Current hosthttps://qr.limkokwing.net/api/create_qrcode_api
Older host (variants)https://qrcode.limkokwing.net/api/create_qrcode_api
Bodyapplication/x-www-form-urlencoded (PHP cURL POSTFIELDS array)
TLS in scriptsCURLOPT_SSL_VERIFYPEER/HOST = false — do not copy into Raisd Live adapters
Ops noteComments mention Cloudflare challenges on the QR host; staff sometimes copy a debug cURL

Request fields

FieldExample / ruleNotes
name Verification Cambodia Cert - {StdProgramID} Newer script makes name unique per serial; older uses fixed label
category LUCT Institution bucket in QR SaaS
subcategory Certificate Accepted by API but not persisted in SaaS DB (category only)
short_code KHM-PNH_{StdProgramID} Campus + city prefix + programme serial
url https://verification.limkokwing.net/cambodia/{token} This is what the QR encodes — not raw student PII
created_by User Literal in observed scripts

Response (from QR System ApiController)

  • JSON; field Qrcode = base64 PNG. Also status (1 success / 0 error-or-exists) and message.
  • If short_code already exists: returns existing PNG with status: 0, message “QR code already exists” (CMS still gets an image).
  • PNG payload data = {base_url}/qrcode/{short_code}, not the Verification URL. Redirect resolves the stored url.
  • Image via Endroid QR Code (300px, medium ECC). Stored under writable/uploads/qrcodes/{short_code}.png.
  • Category auto-created by name if missing (description “Auto-created category”) — how LUCT appeared in the dump.

4. QR System pack (inventoried)

Pack folder qr.limkokwing.net/qrcode-lkwn — CodeIgniter 4 app starter (codeigniter4/appstarter, PHP ≥ 8.1, libraries endroid/qr-code + khanamiryan/qrcode-detector-decoder. Dump file qrcode.sql (MariaDB 10.11, generated 8 Oct 2026, DB name qrcode).

Admin UI routes (session auth)

PathControllerNotes
/, login / logout / forgot / resetQrsystemauth filter on dashboard
/categories, add/editCategoryControllerAdmin CRUD
/users, add/editUserControllerAdmin / SuperAdmin
/add-new-qr, /edit-qrcode/{slug}QrCodeControllerManual QR entry
/qrcode/{slug}QrCodeController::redirectPublic 302 → stored URL
/qrcode-image/{slug}get_imageServes PNG
POST /api/create_qrcode_apiApiControllerNo auth filter in Routes — campus integration entry
POST /api/qrcodes, delete/*, check-slug, …ApiControllerAdmin AJAX helpers

Database qrcode (structure)

TableRoleKey columns
qrcodeQR registryname, slug (= short_code), category_id, url, qr_image_file
categoriesBucketscategory_name, description
usersAdmin accountsusername, bcrypt password, accesslevel Admin|SuperAdmin — hashes not published
forgetpassReset tokenstoken, user_id
migrationsCI4framework bookkeeping

Dump snapshot (counts — no PII republished)

MetricValue
Total qrcode rows (parsed)~697
Cambodia certs (KHM-PNH_* → Verification)685 (category LUCT)
Other QR usesNamecards (LUCT / CCC / Raisd), Events, Publicity, General intake
Categories in dumpGeneral · Events · Publicity · Namecard LUCT · Namecard CCC · Namecard Raisd · LUCT
Other campus short-code prefixes in dumpNone — only KHM-PNH_ among certificate-style slugs

The QR SaaS is therefore a multi-purpose Limkokwing short-link / QR registry (namecards + marketing + certificates), not Cambodia-only. Certificate volume dominates this dump.

5. Verification pack (inventoried)

Zip verification.limkokwing.net_source_code_08_10_2026.zip is small (~13 KiB): a front controller, crypto helper, Cambodia cert model, Apache rewrite, and docker stubs. No full framework tree.

Request path

flowchart TD
  A["GET /{country}/{token}"] --> B[urldecode token]
  B --> C[AES-256-CBC decrypt]
  C --> D[StdProgramID]
  D --> E["CALL GetStudentCertInfo(StdProgramID)"]
  E --> F{Row found?}
  F -->|yes| G[Certificate Verified UI]
  F -->|no| H[No certificate found]
          
  • Apache .htaccess rewrites all non-files to index.php.
  • First path segment is treated as country (UI hard-codes campus label “Cambodia” today).
  • Decrypt supports plain numeric StdProgramID or legacy …_timestamp_date forms (takes trailing segment after last _ when non-numeric).

CMS stored procedure GetStudentCertInfo

Defined on campus2_cambodia (CMS procedures dump 7 Oct 2026). Verification calls it over MySQL with a dedicated DB user (credentials in pack — ops secret, not published).

  • Input: StdProgID (varchar).
  • Reads r_studentcert_cambodia + join s_upload where UploadType in (11) for cert image blob.
  • Returns: StdProgramID, StudentID, StudentName, PicURL, ProgramCode, ProgramName, GraduationDate, CertSN, CertFileBlob, CertFileName, CertFileType.

Verified page fields

  • Student photo (PicURL), name, Student ID
  • Programme name · Campus label “Cambodia” · Graduation date
  • Certificate serial (CertSN) · inline certificate PNG from blob when SN present
  • Footer branding: “Raisd AI”
Ops risk — AES key sync. Pack copies of CMS qrcode_gen.php and Verification lib/Encryption.php use different AES key/IV material (fingerprints differ). Live systems must keep encrypt/decrypt keys aligned; Raisd adapters should take secrets from vault, not from checked-in dumps.

6. Verify token & short code

Public Verification URL

https://verification.limkokwing.net/cambodia/{token}

  • {token} = base64(AES-256-CBC ciphertext of StdProgramID).
  • URL-encoded when appended (urlencode / rawurlencode depending on script).
  • Stored as qrcode.url in the SaaS. The PNG encodes qr.limkokwing.net/qrcode/{short_code}, which 302-redirects here.
  • Key material in Registry + Verification PHP — ops secret; never republish. Packs currently disagree (see §5).

Short-code convention

PartValueMeaning
CountryKHMCambodia (ISO-ish campus code)
City / campusPNHPhnom Penh
Separator_
SerialStdProgramIDStudent–programme row id from CMS

Raisd should treat short-code allocation as owned by the QR SaaS + campus convention — do not invent a parallel allocator in Schema without campus agreement.

Crypto evolution (high level)

  • Current path: OpenSSL AES-256-CBC of StdProgramID alone (commented older path appended _timestamp_YYYYMMDD).
  • Legacy: qrcode_gen1.php / early helpers used mcrypt Rijndael — obsolete; not the create-API path.
  • Local-only QR: early qrcode.php built images with phpqrcode against Verification base URL — superseded by SaaS create API.

7. CMS Registry scripts

All under cambodia/registry/ in the CMS pack (CMS_Cambodia_Source_Codes_&_DB_Structure_07_10_2026). Dates from file mtimes in the extract.

File~DateQR hostRole
qrcode_gen.php Mar 2026 qr.limkokwing.net Current-style — unique name, create API, PNG download, debug comments
qrcode_gen_onthespot.php Jun 2024 qr.limkokwing.net On-the-spot cert path — same API shape
qrcode_gen_18092025.php Jul 2025 qr.limkokwing.net Dated variant — same API shape
qrcode_gen4.php Jul 2025 qrcode.limkokwing.net Older hostname; requires all three GET params
qrcode_gen3.php Jul 2025 qrcode.limkokwing.net Sample hard-coded verify URL (debug residue)
qrcode.php Oct 2023 local phpqrcode Early helper — encrypt + local image; not SaaS create
qrcode_gen1.php Oct 2023 — mcrypt encrypt/decrypt smoke test only

Output artefact

Filename pattern: {StudentID}_{ProgramCode}.png (example defaults in the current script: 601001001_PUFP.png). Served as Content-Disposition: attachment, then unlinked from the web root.

8. Hosts & source packs

External hosts

HostPurpose
qr.limkokwing.net Current QR registry / create API (+ admin UI presumed in QR System pack)
qrcode.limkokwing.net Legacy/alternate create API host still present in older scripts
verification.limkokwing.net Public Verification app; Cambodia path /cambodia/{token}

Drive packs (outside git)

PackSharedLocal dumpStatus for inventory
CMS Cambodia code + DB 7 Oct 2026 raisd/_local/cambodia-cms-2026-10-07/ Done — Registry call site extracted
QR System Source Code & Database 8 Oct 2026 raisd/_local/cambodia-qr-2026-10-08/qr-system/ Done — CI4 + qrcode.sql (§4)
Verification Source Code 8 Oct 2026 …/verification/ Done — front controller + model (§5)

9. Raisd implications

  1. Campus integration, not Portal API core (Lesotho M4). Applicant Demo DEMO PDFs must not pretend to be this certificate QR path.
  2. Three hops on scan: PNG → QR short URL → Verification → live campus2_cambodia via GetStudentCertInfo. Raisd Live must not break the SP or upload-type 11 cert blob join.
  3. QR SaaS is shared infrastructure (namecards + marketing + certs). This dump’s certificate short-codes are Cambodia-only (KHM-PNH_); other campuses may adopt new prefixes later.
  4. M5 adapter: after official cert issuance, POST create API (TLS verify on, secrets in vault). Expect idempotent “already exists” responses. Prefer documenting Cloudflare allow-lists over disabling SSL verification.
  5. AES keys must match between encrypt (Registry/adapter) and decrypt (Verification). Do not ship pack keys into git; resolve live key ownership with campus ops.
  6. Verification DB access is a direct MySQL read of CMS — treat as high-trust path (network allow-list, least-privilege user, no credentials in repos).

Architecture sketch (current + Raisd Live)

flowchart LR
  subgraph Issue["Issue path"]
    REG[Registry qrcode_gen]
    ADAPT[Future Raisd ACL]
  end
  subgraph QRSaaS["qr.limkokwing.net"]
    API[create_qrcode_api]
    REDIR["/qrcode/slug → 302"]
  end
  subgraph Verify["verification.limkokwing.net"]
    FRONT[index.php decrypt]
  end
  subgraph SoR["campus2_cambodia"]
    SP[GetStudentCertInfo]
  end
  REG --> API
  ADAPT -.-> API
  API --> REDIR
  REDIR --> FRONT
  FRONT --> SP
          
Publish target. GitHub Pages — certificate-qr.html · Markdown cambodia-qr-system.md. Inventory closed for CMS call site + QR System + Verification source packs (8 Oct 2026).