Getting Started: Concepts and Prerequisites
ជំពូកនេះមិនទាន់ត្រូវបានបកប្រែជាភាសាខ្មែរនៅឡើយទេ — សូមអានជាភាសាអង់គ្លេសខាងក្រោម។
Table of Contents
- What you're building
- How a KHQR payment flows
- Key concepts
- A note on the package choice
- Prerequisites
- Beginner Pitfalls
1. What you're building
By the end of this series, you'll have a page that generates a scannable Bakong KHQR code and tells you the moment it's paid, using three steps:
- Generate a KHQR code — an EMVCo-format string any Bakong-participating banking app can scan and pay.
- Render it as a scannable image — the generated value is just text, not a picture.
- Check whether it's been paid — by polling the Bakong Open API with a hash of the QR.
The biggest thing to sit with before writing any code: step 1 needs no payment gateway account and no secret key. That's a real structural difference from a typical integration like this site's ABA PayWay Tutorial series, where every request has to be signed before PayWay will accept it.
2. How a KHQR payment flows
This is the flow the National Bank of Cambodia describes in its QR Payment Integration guide (PDF), with the QR generated on the merchant's backend — the same setup this series builds:
- The customer starts a payment on your website or app.
- Your backend generates the KHQR with NBC's KHQR SDK (or by following the EMVCo QR standard directly), then returns the QR, its MD5 hash, and its expiry time to your frontend.
- Your frontend shows the QR to the customer, along with when it expires.
- The customer scans it with the Bakong app or any bank app that supports KHQR — in practice, all Bakong member banks.
- The customer confirms the payment in their app.
- Your backend checks the payment status with Bakong's Open API, using the MD5 hash, on a repeating interval (you choose how often) until the QR expires.
- Bakong answers with the payment's status.
- You show the result to the customer, as a message on the page or a push notification.
Customer Frontend Your backend Bakong
| | | |
|-- start ------>| | |
| |-- create QR ----->| |
| | |-- generate KHQR |
| | | (offline, SDK) |
| |<-- QR, md5, ------| |
| | expiry | |
|<-- show QR ----| | |
| |
|-- scan & confirm in bank app ------------------------->|
| |
| |-- check status -->|-- check by md5 -->|
| | |<-- status --------|
| |<-- paid / not ----| |
|<-- "Paid!" ----| yet (repeat) | |
When your backend checks the status (step 6), handle each result differently:
| Bakong says | What to do |
|---|---|
| Success | Mark the order paid and continue (confirm to the customer, print a receipt, etc.). |
| Failed | Mark the transaction failed and tell the customer. |
| Not found, QR not expired yet | Not paid yet — keep checking. |
| Not found, QR expired | Mark it timed out and ask the customer to pay again with a new QR. |
For step 8, NBC describes two ways to get the result to the customer's screen: your backend pushes it to the frontend (e.g. over a WebSocket), or your frontend asks your backend repeatedly. This series uses the second, simpler option — see Part 3.
NBC also says a QR's expiry shouldn't exceed 10 minutes. If a payment still hasn't been confirmed by then, the fallback is a manual check: ask the customer to show the transaction in their Bakong or bank app.
3. Key concepts
| Term | What it means |
|---|---|
| KHQR | Cambodia's standardized QR payment format (built on the EMVCo QR standard), issued by the National Bank of Cambodia. Any participating bank/e-wallet app can scan it — it isn't tied to one bank. |
| Bakong Account ID | The identifier of who gets paid — looks like someone@devb. It is not secret; it functions like a bank account number, not a password. |
| KHQR string vs. KHQR image | Generating a KHQR produces a plain text string in EMVCo format — not a picture. You run that string through a separate QR-image library to get something a phone camera can scan. This trips up almost everyone the first time. |
| Static vs. dynamic KHQR | A KHQR with no amount (amount omitted/zero) is static — reusable, no expiry, the payer types in how much to pay. A KHQR with an amount set is dynamic — one specific charge — and it requires an expirationTimestamp. This series builds a dynamic KHQR. |
| MD5 hash | Generating a KHQR also produces an MD5 hash of the QR string. This hash isn't shown to the payer — it's what you use afterward to ask the Bakong API "has this specific QR been paid yet?" |
| Bearer token / access token | A credential from the Bakong Open API portal that authorizes calls to check payment status. It's emailed to you after registering, expires after 90 days (NBC emails a reminder 3 days before), and is renewed from the same portal. Unlike the Account ID, this is sensitive — keep it server-side. Full steps in Part 3. |
4. A note on the package choice
This series uses bakong-khqr,
NBC's official KHQR SDK — the same package used in the National Bank of
Cambodia's own KHQR SDK Document (PDF).
Its source isn't developed in public (its npm listing points to NBC's own
GitLab, which isn't publicly reachable), but the published package ships
readable JavaScript, so you can open node_modules/bakong-khqr/src to see
exactly what it does.
It ships no TypeScript types. If you're using TypeScript, the
community-written @types/bakong-khqr package adds them, though it leaves
the generate result (data) typed as unknown. There's also
ts-khqr, an independent
community reimplementation of the KHQR standard with its own types —
it doesn't use bakong-khqr at all, so it isn't guaranteed to match the
official SDK in every edge case.
Bakong's canonical docs are PDFs — the KHQR SDK Document above and the Bakong Open API Document (PDF) — unlike ABA PayWay's HTML developer portal, which makes a guide like this one easy to silently drift from as they're updated. Check them before shipping to production.
5. Prerequisites
- A Bakong Account ID — the account a generated KHQR will pay into.
- A Bakong Open API access token — register at api-bakong.nbc.gov.kh/register (it can take up to 24 hours to arrive by email, so do this first); only needed for the status-check step (Part 3), not for generating a KHQR.
- A basic web page you can edit.
- A backend you can run locally (this series' example uses Node.js/Express, but any language works).
6. Beginner Pitfalls
- Don't assume generating a KHQR requires network access or an access token. It doesn't — only the status-check step does.
- Don't confuse the Bakong Account ID (public, like a bank account number) with the access token (sensitive, server-side only). Mixing these up is an easy but consequential mistake.
- Don't mix up
bakong-khqr(NBC's official SDK, used here) with community packages likets-khqr— their APIs differ, so code copied from a guide using one won't run with the other.
ទិញកាហ្វេជូនខ្ញុំ
ចូលចិត្តមេរៀននេះមែនទេ? សូមពិចារណាទិញកាហ្វេជូនខ្ញុំមួយកែវ ដើម្បីគាំទ្រការបង្កើតមាតិកាឥតគិតថ្លៃបន្ថែមទៀត!