Flutter Bakong Payway
A Flutter plugin for generating Bakong KHQR payment codes. Supports Android, iOS, and Web platforms.
KHQR is Cambodia's centralized QR code payment system - Scan. Pay. Done.
☕ Support & Donate
If you find this package helpful, consider supporting the development!
KUN VENG ANN
Scan with any Bakong-supported banking app
Features
- ✅ Generate Individual KHQR codes (personal accounts)
- ✅ Generate Merchant KHQR codes (business accounts)
- ✅ Support for USD and KHR currencies
- ✅ Cross-platform: Android, iOS, and Web
- ✅ Easy-to-use API with type-safe models
- ✅ Auto-configuration (minimal setup required)
Installation
Add this to your pubspec.yaml:
dependencies:
flutter_bakong_payway: ^1.0.0
Then run:
flutter pub get
Platform Setup
Android
No additional setup required. The plugin automatically includes the BakongKHQR SDK.
iOS
No additional setup required. The plugin embeds the BakongKHQR SDK via CocoaPods.
Web
Web requires 2 JavaScript files in your web/ folder:
Step 1: Download the SDK
Download the BakongKHQR SDK:
# Download from Gist
curl -L -o web/khqr-sdk.min.js "https://gist.githubusercontent.com/KunvengAnn/65f9efd653da42defb3466c3cd52a2a2/raw/khqr-sdk.min.js"
Or manually download from: https://gist.github.com/KunvengAnn/65f9efd653da42defb3466c3cd52a2a2
Save it to your web/ folder as khqr-sdk.min.js
Step 2: Create the Wrapper File
Create a new file web/khqr_wrapper.js with this content:
// khqr_wrapper.js - Wrapper for BakongKHQR JavaScript SDK
(function () {
"use strict";
function checkKHQR() {
if (typeof BakongKHQR === "undefined") {
throw new Error("BakongKHQR SDK not loaded. Include khqr script first.");
}
}
window.flutterBakongKHQR = {
generateIndividual: function (info) {
return new Promise(function (resolve, reject) {
try {
checkKHQR();
var sdk = BakongKHQR;
var khqr = new sdk.BakongKHQR();
var khqrData = sdk.khqrData;
var currencyStr = (info.currency || "USD").toUpperCase();
var currency =
currencyStr === "KHR"
? khqrData.currency.khr
: khqrData.currency.usd;
var optionalData = {
currency: currency,
amount: info.amount || 0,
billNumber: info.billNumber || "",
mobileNumber: info.mobileNumber || "",
storeLabel: info.storeLabel || "",
terminalLabel: info.terminalLabel || "",
purposeOfTransaction: info.purposeOfTransaction || "",
merchantCategoryCode: info.merchantCategoryCode || "5999",
};
if (info.expirationTimestamp) {
optionalData.expirationTimestamp = info.expirationTimestamp;
} else if (info.amount && info.amount > 0) {
optionalData.expirationTimestamp = Date.now() + 5 * 60 * 1000;
}
var individualInfo = new sdk.IndividualInfo(
info.bakongAccountId || info.accountId || "",
info.merchantName || "",
info.merchantCity || "Phnom Penh",
optionalData
);
var response = khqr.generateIndividual(individualInfo);
if (response && response.data) {
resolve({
qr: response.data.qr || "",
md5: response.data.md5 || "",
});
} else {
reject(new Error("Failed to generate KHQR"));
}
} catch (e) {
reject(e);
}
});
},
generateMerchant: function (info) {
return new Promise(function (resolve, reject) {
try {
checkKHQR();
var sdk = BakongKHQR;
var khqr = new sdk.BakongKHQR();
var khqrData = sdk.khqrData;
var currencyStr = (info.currency || "USD").toUpperCase();
var currency =
currencyStr === "KHR"
? khqrData.currency.khr
: khqrData.currency.usd;
var optionalData = {
currency: currency,
amount: info.amount || 0,
billNumber: info.billNumber || "",
mobileNumber: info.mobileNumber || "",
storeLabel: info.storeLabel || "",
terminalLabel: info.terminalLabel || "",
purposeOfTransaction: info.purposeOfTransaction || "",
merchantCategoryCode: info.merchantCategoryCode || "5999",
};
if (info.expirationTimestamp) {
optionalData.expirationTimestamp = info.expirationTimestamp;
} else if (info.amount && info.amount > 0) {
optionalData.expirationTimestamp = Date.now() + 5 * 60 * 1000;
}
var merchantInfo = new sdk.MerchantInfo(
info.bakongAccountId || info.accountId || "",
info.merchantName || "",
info.merchantCity || "Phnom Penh",
info.merchantId || "",
info.acquiringBank || "",
optionalData
);
var response = khqr.generateMerchant(merchantInfo);
if (response && response.data) {
resolve({
qr: response.data.qr || "",
md5: response.data.md5 || "",
});
} else {
reject(new Error("Failed to generate KHQR"));
}
} catch (e) {
reject(e);
}
});
},
};
console.log("flutterBakongKHQR wrapper loaded");
})();
Step 3: Update index.html
Add these scripts to your web/index.html before the Flutter bootstrap script:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Your App</title>
<!-- BakongKHQR SDK - Add BEFORE flutter.js -->
<script src="khqr-sdk.min.js"></script>
<!-- KHQR Wrapper - Add BEFORE flutter.js -->
<script src="khqr_wrapper.js"></script>
</head>
<body>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>
Your web/ folder structure:
web/
├── index.html (modified)
├── khqr-sdk.min.js (downloaded - ~55KB)
├── khqr_wrapper.js (created above)
├── manifest.json
└── icons/
Important: The scripts must be loaded before Flutter initializes.
Usage
Import the package
import 'package:flutter_bakong_payway/flutter_bakong_payway.dart';
Generate Individual KHQR
final bakong = FlutterBakongPayway();
final info = IndividualInfo(
accountId: 'your_account@bank',
merchantName: 'John Doe',
currency: KHQRCurrency.usd,
amount: 10.00,
);
final result = await bakong.generateIndividualQR(info);
print('QR Data: ${result?.qr}');
print('MD5: ${result?.md5}');
Generate Merchant KHQR
final bakong = FlutterBakongPayway();
final info = MerchantInfo(
accountId: 'merchant@bank',
merchantId: 'M001',
merchantName: 'ABC Store',
acquiringBank: 'ABA Bank',
currency: KHQRCurrency.khr,
amount: 50000.0,
merchantCity: 'Phnom Penh',
);
final result = await bakong.generateMerchantQR(info);
print('QR Data: ${result?.qr}');
print('MD5: ${result?.md5}');
Currency Options
// US Dollar
currency: KHQRCurrency.usd
// Khmer Riel
currency: KHQRCurrency.khr
Display QR Code
Use any QR code package like qr_flutter:
import 'package:qr_flutter/qr_flutter.dart';
QrImageView(
data: result?.qr ?? '',
version: QrVersions.auto,
size: 250,
)
API Reference
IndividualInfo
| Parameter | Type | Required | Description |
|---|---|---|---|
accountId |
String | ✅ | Bakong account ID (e.g., "user@bank") |
merchantName |
String | ✅ | Account holder name |
currency |
KHQRCurrency | ✅ | USD or KHR |
amount |
double | ✅ | Payment amount |
merchantCity |
String | ❌ | City (default: "Phnom Penh") |
billNumber |
String | ❌ | Invoice/bill number |
mobileNumber |
String | ❌ | Contact phone number |
storeLabel |
String | ❌ | Store name |
terminalLabel |
String | ❌ | Terminal identifier |
expirationTimestamp |
int | ❌ | Expiration time (auto-set if amount > 0) |
MerchantInfo
| Parameter | Type | Required | Description |
|---|---|---|---|
accountId |
String | ✅ | Bakong account ID |
merchantId |
String | ✅ | Merchant ID |
merchantName |
String | ✅ | Business name |
acquiringBank |
String | ✅ | Bank name |
currency |
KHQRCurrency | ✅ | USD or KHR |
amount |
double | ✅ | Payment amount |
merchantCity |
String | ❌ | City (default: "Phnom Penh") |
billNumber |
String | ❌ | Invoice/bill number |
mobileNumber |
String | ❌ | Contact phone number |
storeLabel |
String | ❌ | Store name |
terminalLabel |
String | ❌ | Terminal identifier |
expirationTimestamp |
int | ❌ | Expiration time (auto-set if amount > 0) |
KHQRData (Response)
| Field | Type | Description |
|---|---|---|
qr |
String | The KHQR string to encode as QR code |
md5 |
String | MD5 hash of the QR data |
Example
See the example folder for a complete demo app.
cd example
flutter run
Notes
- When
amount > 0, an expiration timestamp is automatically set to 5 minutes from now if not provided. - The plugin handles all platform-specific configurations internally.
- For Web, ensure the SDK scripts are loaded before Flutter initializes.
License
MIT License - see LICENSE for details.
Credits
- BakongKHQR SDK by National Bank of Cambodia
- bakong-khqr npm package for Web support