bakong-khqr-image
Generate styled Bakong KHQR card images that match the official NBC design — with correct USD and KHR amount formatting.
Built from production experience: the official
bakong-khqrlibrary formats USD amounts with a comma (0,01 USD) instead of a period (0.01 USD). This library fixes that and ships a clean, standalone implementation.
Features
- ✅ EMV-compliant KHQR payload generation per KHQR spec v1.3 (no external bakong dependency)
- ✅ Correct amount formatting —
1,234.56USD ·4,000KHR - ✅ Styled 300 × 450 px card (Bakong red header, merchant info, dashed separator, rounded corners)
- ✅ Bundled assets — works out of the box, no manual font/icon setup
- ✅ Multiple export formats: PNG · JPEG · WebP · bytes · Base64 · data URI
- ✅ Static (open-amount) and dynamic QR code support
- ✅ Individual & merchant account types — correct Tag 15 / Tag 29 structure
- ✅ Tag 99 transaction timestamps — required for Bakong backend to process payments
- ✅
from_emv()— render a styled card from any existing raw KHQR string - ✅ Typed, documented, tested
Installation
pip install bakong-khqr-image
Dependencies (Pillow and qrcode) are installed automatically.
Quick Start
from bakong_khqr_image import KHQRImage
# Create one instance per merchant — reuse it for every payment
khqr = KHQRImage(
bakong_account_id="yourname@aclb",
merchant_name="My Coffee Shop",
merchant_city="Phnom Penh", # optional, default "Phnom Penh"
currency="USD", # optional, default "USD"
account_type="individual", # optional, default "individual" (personal wallet)
# use "merchant" for registered KHQR merchants
)
# Generate a styled QR card image
result = khqr.generate_image(amount=2.50)
# Save as PNG
result.save_png("payment_qr.png")
# Or get bytes / Base64 / data URI for web / API responses
png_bytes = result.to_bytes()
b64 = result.to_base64()
data_uri = result.to_data_uri()
API Reference
KHQRImage
KHQRImage(
bakong_account_id: str,
merchant_name: str,
merchant_city: str = "Phnom Penh",
currency: str = "USD", # "USD" or "KHR"
account_type: str = "individual", # "individual" or "merchant"
)
generate_image(amount, *, bill_number, store_label, terminal_label, mobile_number, static, include_additional_data, ttl_seconds) → QRImageResult
Renders the full styled card image.
| Parameter | Type | Default | Description |
|---|---|---|---|
amount |
float |
Transaction amount. 0 = open-amount / static QR |
|
bill_number |
str |
None |
Unique reference ID (auto-generated UUID if omitted) |
store_label |
str |
None |
Store label shown in the Bakong app |
terminal_label |
str |
None |
Terminal identifier |
mobile_number |
str |
None |
Merchant phone number |
static |
bool |
False |
True → static QR (no amount, no Tag 99) |
include_additional_data |
bool |
False |
True → embed Tag 62 (bill/store/terminal) in payload |
ttl_seconds |
int |
60 |
Transaction validity window for Tag 99. 0 = omit |
generate_payload(amount, *, ...) → (payload, bill_number)
Returns only the raw EMV KHQR string — useful for custom renderers or the Bakong payment-verification API.
payload, ref_id = khqr.generate_payload(amount=5.00)
md5_hash = KHQRImage.md5(payload) # pass to Bakong API to check payment status
KHQRImage.from_emv(emv_string) → QRImageResult
Generate a styled card image directly from an existing raw KHQR / EMV string. Merchant name, amount, and currency are extracted automatically from Tags 59, 54, and 53.
emv = "00020101021229240020yourname@aclb...6304ABCD"
result = KHQRImage.from_emv(emv)
result.save_png("from_emv.png")
KHQRImage.md5(payload) → str
Computes the MD5 hash of a payload string required by the Bakong transaction-check API.
QRImageResult
| Method | Returns | Description |
|---|---|---|
save_png(path) |
str |
Save PNG, returns absolute path |
save_jpeg(path) |
str |
Save JPEG, returns absolute path |
save_webp(path) |
str |
Save lossless WebP, returns absolute path |
to_bytes() |
bytes |
PNG-encoded bytes |
to_base64() |
str |
Base64-encoded PNG string |
to_data_uri() |
str |
data:image/png;base64,… URI |
.image |
PIL.Image |
Raw PIL Image for custom processing |
Examples
KHR currency
from bakong_khqr_image import KHQRImage
khqr = KHQRImage(
bakong_account_id="yourname@aclb",
merchant_name="ហាងកាហ្វេ",
currency="KHR",
)
result = khqr.generate_image(amount=4_000)
result.save_png("payment_khr.png")
Static (open-amount) QR
# Customer enters the amount inside the Bakong app — useful for printed menus
result = khqr.generate_image(amount=0, static=True)
result.save_png("static_qr.png")
Individual vs. merchant account type
# Default — personal Bakong wallet (Tag 29 only, no Tag 15)
khqr_personal = KHQRImage(
bakong_account_id="yourname@aclb",
merchant_name="My Shop",
account_type="individual", # default, can be omitted
)
# Registered KHQR merchant (adds Tag 15 with AID per KHQR spec v1.3)
khqr_merchant = KHQRImage(
bakong_account_id="merchant@wbkh",
merchant_name="Virackbot Mart",
account_type="merchant",
)
Render a card from an existing KHQR string
# No need to re-generate — just pass the raw EMV string
emv = "00020101021229240020yourname@aclb...6304ABCD"
result = KHQRImage.from_emv(emv)
result.save_png("from_emv.png")
Web / API response
result = khqr.generate_image(amount=1.50)
# Flask / Django — return as JSON
response = {"qr_image": result.to_data_uri()}
# Or stream PNG bytes directly
return Response(result.to_bytes(), mimetype="image/png")
Check payment status with the Bakong API
import requests
payload, ref_id = khqr.generate_payload(amount=5.00)
md5 = KHQRImage.md5(payload)
resp = requests.post(
"https://api-bakong.nbc.org.kh/v1/check_transaction_by_md5",
headers={"Authorization": "Bearer <YOUR_TOKEN>"},
json={"md5": md5},
)
if resp.json().get("responseCode") == 0:
print("Payment received!")
Access the raw PIL image
result = khqr.generate_image(amount=2.50)
pil_image = result.image # PIL.Image.Image
pil_image.show() # open in system viewer
📁 See the
examples/folder for full runnable scripts:
basic_usage.py— core workflowkhr_and_static.py— KHR & static QRaccount_types.py— individual vs. merchantfrom_emv.py— render from existing EMV stringadvanced_options.py— Tag 62, TTL, all export formatsweb_api_example.py— Flask / Django integration
Card Layout
┌─────────────────────────────┐ ◥ red fold
│ [KHQR / Bakong logo] │ ← red header (60 px)
│ │
│ Merchant Name │
│ 2.50 USD │
│ - - - - - - - - - - - - - - │ ← dashed separator
│ │
│ ┌───────────────────┐ │
│ │ │ │
│ │ QR Code │ │
│ │ [💲] │ │ ← currency icon overlay
│ │ │ │
│ └───────────────────┘ │
│ │
└─────────────────────────────┘
300 × 450 px
Sample image
Running Tests
pip install -e ".[dev]"
pytest
Publishing to PyPI
pip install build twine
python -m build
twine upload dist/*
License
MIT © 2026 Virackbot
Metadata
Release files for bakong-khqr-image 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| bakong_khqr_image-0.1.4.tar.gz | 210.1 kB | With provenance | Details for bakong_khqr_image-0.1.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| bakong_khqr_image-0.1.4-py3-none-any.whl | Python 3 | none | any | Details for bakong_khqr_image-0.1.4-py3-none-any.whl |
Total release size: 414.8 kB
Release files / bakong_khqr_image-0.1.4.tar.gz
| Download URL | bakong_khqr_image-0.1.4.tar.gz |
|---|---|
| Size | 210.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bf67d7056a4503c3805805e3fed2df654a5bc3fc4bdcf657003848cc3e1f6c1c
|
|
BLAKE2b-256 checksum How to use checksums |
418e5bd5cd7202779f17f732e20215c8399dd9d905cb261178060f161afb58ca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 5, 2026.
Transparency logRelease files / bakong_khqr_image-0.1.4-py3-none-any.whl
| Download URL | bakong_khqr_image-0.1.4-py3-none-any.whl |
|---|---|
| Size | 204.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e3d088800f012aae9a5475c8bc399d0d0cea3a6ad9105cebbbf2f31aeb38e1f4
|
|
BLAKE2b-256 checksum How to use checksums |
d2319345b52ff03cb66ebae248b4b5253f1c25c9b20dd479f284871d93606fff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 5, 2026.
Transparency logSecurity for bakong-khqr-image 0.1.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for bakong_khqr_image-0.1.4.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 5, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.