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.

pub package License: MIT

☕ Support & Donate

If you find this package helpful, consider supporting the development!

Donate via KHQR
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