Metadata-Version: 2.4
Name: bakong-khqr-image
Version: 0.1.4
Summary: Generate styled Bakong KHQR card images with correct USD/KHR amount formatting.
Author-email: Virackbot <virackbot@example.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/Virackbot/bakong-khqr-image
Project-URL: Repository, https://github.com/Virackbot/bakong-khqr-image
Project-URL: Bug Tracker, https://github.com/Virackbot/bakong-khqr-image/issues
Keywords: bakong,khqr,cambodia,qrcode,payment,emv
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Pillow>=10.0
Requires-Dist: qrcode[pil]>=7.4
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Dynamic: license-file

# bakong-khqr-image

[![PyPI version](https://img.shields.io/pypi/v/bakong-khqr-image.svg)](https://pypi.org/project/bakong-khqr-image/)
[![Python versions](https://img.shields.io/pypi/pyversions/bakong-khqr-image.svg)](https://pypi.org/project/bakong-khqr-image/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

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-khqr` library 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.56` USD · `4,000` KHR
- ✅ 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

```bash
pip install bakong-khqr-image
```

Dependencies (`Pillow` and `qrcode`) are installed automatically.

---

## Quick Start

```python
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`

```python
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.

```python
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.

```python
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

```python
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

```python
# 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

```python
# 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

```python
# 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

```python
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

```python
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

```python
result = khqr.generate_image(amount=2.50)
pil_image = result.image          # PIL.Image.Image
pil_image.show()                  # open in system viewer
```

> 📁 See the [`examples/`](examples/) folder for full runnable scripts:
> - [`basic_usage.py`](examples/basic_usage.py) — core workflow
> - [`khr_and_static.py`](examples/khr_and_static.py) — KHR & static QR
> - [`account_types.py`](examples/account_types.py) — individual vs. merchant
> - [`from_emv.py`](examples/from_emv.py) — render from existing EMV string
> - [`advanced_options.py`](examples/advanced_options.py) — Tag 62, TTL, all export formats
> - [`web_api_example.py`](examples/web_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
![USD QR sample](https://i.imgur.com/cabzQmf.png)
![KHR QR sample](https://i.imgur.com/3nssiDC.png)

## Running Tests

```bash
pip install -e ".[dev]"
pytest
```

---

## Publishing to PyPI

```bash
pip install build twine
python -m build
twine upload dist/*
```

---

## License

[MIT](LICENSE) © 2026 Virackbot
