Metadata-Version: 2.5
Name: einvoice-tw
Version: 0.1.0
Summary: Parse Taiwan e-invoice (電子發票) QR codes and barcodes into structured data, and check them against the uniform-invoice lottery.
Project-URL: Homepage, https://github.com/Aaron-lab-c/einvoice-tw
Project-URL: Repository, https://github.com/Aaron-lab-c/einvoice-tw
Project-URL: Issues, https://github.com/Aaron-lab-c/einvoice-tw/issues
Project-URL: Changelog, https://github.com/Aaron-lab-c/einvoice-tw/blob/main/CHANGELOG.md
Author: ARON
License-Expression: MIT
License-File: LICENSE
Keywords: accounting,e-invoice,einvoice,invoice,lottery,qr,qrcode,receipt,taiwan,對獎,統一發票,電子發票
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Natural Language :: Chinese (Traditional)
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Accounting
Classifier: Topic :: Office/Business :: Financial :: Point-Of-Sale
Classifier: Topic :: Utilities
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# einvoice-tw

**Parse Taiwan e-invoice (電子發票) QR codes into structured data — and check them against the uniform-invoice lottery.**
Pure Python, zero dependencies, follows the Ministry of Finance barcode specification.

[![PyPI](https://img.shields.io/pypi/v/einvoice-tw.svg)](https://pypi.org/project/einvoice-tw/)
[![Python](https://img.shields.io/pypi/pyversions/einvoice-tw.svg)](https://pypi.org/project/einvoice-tw/)
[![CI](https://github.com/Aaron-lab-c/einvoice-tw/actions/workflows/ci.yml/badge.svg)](https://github.com/Aaron-lab-c/einvoice-tw/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/Aaron-lab-c/einvoice-tw/blob/main/LICENSE)

Every receipt printed in Taiwan carries two QR codes (and a Code 39 barcode)
that encode the invoice number, date, random code, amounts, buyer/seller tax
IDs and the line items. `einvoice-tw` turns the text your scanner reads out
of them into a tidy `Invoice` object, JSON or CSV — the building block for
expense trackers, bookkeeping imports, receipt archives and lottery checkers.

[中文說明在下方](#中文說明)

---

## Install

```bash
pip install einvoice-tw
```

Python 3.9+. No dependencies. Scanning the image is *not* part of this
package — point any QR reader (phone app, `zbar`, `pyzbar`, OpenCV…) at the
receipt and feed the resulting text in.

## Python API

```python
import einvoice_tw

left  = "AB112233441020523999900000144000001540000000001234567ydXZt4LAN1UHN/j1juVcRA==:**********:3:3:0:乾電池:1:105:"
right = "**口罩:1:210:牛奶:1:25"          # the second QR code, always starts with **

inv = einvoice_tw.parse(left, right)      # order does not matter; right is optional

inv.number          # 'AB11223344'          inv.track 'AB', inv.serial '11223344'
inv.date            # datetime.date(2013, 5, 23)   (converted from ROC 1020523)
inv.random_code     # '9999'
inv.sales_amount    # 324   (未稅銷售額, 0 when the issuer did not separate it)
inv.tax_amount      # 16    (None when sales_amount is 0)
inv.total_amount    # 340
inv.buyer_ubn       # None  (consumer) or '22099131'
inv.seller_ubn      # '01234567'
inv.items           # [Item(name='乾電池', quantity=Decimal('1'), unit_price=Decimal('105')), ...]
inv.items[0].amount # Decimal('105')
inv.items_total     # 3     how many items the whole invoice has
inv.is_complete     # True  the QR codes contain every item
inv.period          # '10206'  lottery period (102年05-06月), inv.period_label
inv.barcode         # '10206AB112233449999'  content of the Code 39 barcode
inv.encoding        # 'big5' | 'utf-8' | 'base64'

inv.to_dict()       # JSON-ready dict with everything above
inv.to_json()
inv.item_rows()     # one flat dict per item -> csv.DictWriter / pandas.DataFrame
print(inv)          # readable summary

einvoice_tw.parse_barcode("*10404UZ176908720122*")   # Barcode(period='10404', number='UZ17690872', random_code='0122')
einvoice_tw.parse_any(code1, code2)                  # auto-detects barcode / left / right
```

### Lottery (統一發票對獎)

```python
from einvoice_tw import WinningNumbers, check

numbers = WinningNumbers(
    period="10206",                       # 102年05-06月
    special="12345678",                   # 特別獎 1,000 萬
    grand="87654321",                     # 特獎 200 萬
    first=["11223344", "55667788", "99887766"],   # 頭獎 (prizes 2-6 by suffix)
    additional=["007"],                   # 增開六獎 (3 digits), optional
)

check(inv, numbers)            # Prize(name='頭獎', name_en='First Prize', amount=200000, matched='11223344', digits=8)
check("AB00000344", numbers)   # Prize(name='六獎', amount=200, matched='344', ...)
check("AB00000000", numbers)   # None
```

Passing a parsed `Invoice` (or `Barcode`) also verifies that it belongs to the
same period as the winning numbers, raising `PeriodMismatchError` otherwise
(`verify_period=False` to skip). `WinningNumbers.from_json("11306.json")`
loads the same fields from a file.

### 統一編號 checksum

```python
einvoice_tw.is_valid_ubn("04595257")   # True  — implements the rule in force since 2023-04
```

## Command line

```bash
einvoice-tw parse "<left QR text>" "<right QR text>"      # readable summary
einvoice-tw parse --json "<left QR text>"                 # JSON
einvoice-tw parse --csv < scans.txt > items.csv           # one CSV row per item, many invoices
einvoice-tw parse "*10404UZ176908720122*"                 # barcode

einvoice-tw check AB11223344 --period 11306 \
    --special 12345678 --grand 87654321 --first 11111111 22222222 33333333 --additional 123
einvoice-tw check --numbers 11306.json < scans.txt        # numbers from JSON; exit code 1 if any could not be checked

einvoice-tw ubn 04595257 12345678                         # validate 統一編號
```

`stdin` takes one scanned code per line; a line starting with `**` is the
right QR code of the invoice next to it, so you can dump a whole scanning
session into one file and convert it in one go. `--json` prints an object
when a single invoice is given as arguments and a list in every other case
(stdin input always yields a list), so scripts get a predictable shape.

## What it handles

- **The full left-QR header** (77 characters): invoice number, ROC date, random
  code, hex sales/total amounts, buyer and seller UBN, encryption field —
  then the seller area, item counts, encoding flag and the items.
- **All three item encodings**: Big5 (`0`), UTF-8 (`1`, and `3` for
  cross-border e-commerce) and Base64 (`2`) — including Base64 that is encoded
  per code (as in the official examples) *or* encoded once and split across
  both codes, with or without padding.
- **Items split across the two codes**, trailing colons, leftover fields,
  incomplete item lists (`items_in_qr` < `items_total`), decimal quantities,
  thousands separators, and non-numeric quantities (kept as text in
  `item.raw`).
- **Real-world sloppiness** in the default lenient mode: swapped codes,
  lowercase track letters, a seller area shorter than 10 characters, a right
  code missing its `**`, invalid Base64. `strict=True` / `--strict` rejects
  all of these.
- **Mojibake repair**: Big5/UTF-8 bytes that a scanner decoded as Latin-1
  (`°®¹q¦À` or `ä¹¾é»æ±`) are restored to `乾電池` automatically — only when
  the result actually contains CJK, so genuine `30°C` or `Café` stay as they
  are (`fix_mojibake=False` / `--no-fix` disables it).
- **統一編號 helpers**: `is_valid_ubn()` rejects malformed numbers, the
  `00000000` placeholder and non-ASCII digits.
- **Lottery rules**: 特別獎, 特獎, 頭獎 and the 7- to 3-digit suffix prizes
  (二獎 to 六獎), 增開六獎, best prize across all 頭獎 numbers, period check.

## Notes

- The 24-character `encrypt` field is AES-encrypted with a key only the
  issuer and the Ministry of Finance hold; it is exposed verbatim but cannot be
  verified here.
- `sales_amount` is frequently `0` on B2C receipts because many issuers do
  not separate tax for consumers; `total_amount` is always filled.
- Winning numbers are *not* downloaded automatically (the official API needs
  a registered app ID). Type them in or keep them in a small JSON file.
- Specification: 財政部財政資訊中心《電子發票證明聯一維及二維條碼規格說明》.

## Development

```bash
git clone https://github.com/Aaron-lab-c/einvoice-tw.git
cd einvoice-tw
pip install -e . pytest
pytest
```

Releases are published to PyPI automatically by GitHub Actions when a `v*`
tag is pushed (see `.github/workflows/publish.yml`).

Bug reports and ideas: https://github.com/Aaron-lab-c/einvoice-tw/issues

## License

MIT © ARON

---

## 中文說明

**解析台灣電子發票證明聯上的 QR code 與一維條碼，並支援統一發票對獎。**
純 Python、零相依套件，依財政部《電子發票證明聯一維及二維條碼規格說明》實作。

每張電子發票下方的兩個 QR code 裡藏著發票號碼、日期、隨機碼、銷售額、總計、買賣方統編和品項明細。
`einvoice-tw` 把掃描器讀出來的文字變成乾淨的 `Invoice` 物件、JSON 或 CSV，拿來做記帳 App、報帳匯入、發票存檔、自動對獎都很方便。

### 安裝

```bash
pip install einvoice-tw
```

掃描影像不在本套件範圍：用手機、`zbar`、`pyzbar`、OpenCV 等任何 QR 讀取器把文字讀出來後丟進來即可。

### Python 用法

```python
import einvoice_tw

inv = einvoice_tw.parse(左邊QR文字, 右邊QR文字)   # 右邊可省略，順序顛倒也會自動修正
inv.number, inv.date, inv.random_code          # 'AB11223344', date(2013, 5, 23), '9999'
inv.sales_amount, inv.tax_amount, inv.total_amount
inv.buyer_ubn, inv.seller_ubn                  # 買方統編（一般消費者為 None）、賣方統編
inv.items                                      # [Item(name='乾電池', quantity=Decimal('1'), unit_price=Decimal('105')), ...]
inv.period                                     # '10206' → 對獎期別 102年05-06月
inv.to_json()                                  # 全部欄位的 JSON
inv.item_rows()                                # 每個品項一列，可直接丟 csv / pandas

einvoice_tw.parse_barcode("*10404UZ176908720122*")   # 一維條碼：期別 + 發票號碼 + 隨機碼
```

對獎：

```python
from einvoice_tw import WinningNumbers, check

本期 = WinningNumbers(period="11306", special="12345678", grand="87654321",
                      first=["11111111", "22222222", "33333333"], additional=["123"])
check(inv, 本期)              # Prize(name='頭獎', amount=200000, ...) 或 None
check("AB00000123", 本期)     # 也可以直接給發票號碼
```

給 `Invoice` 時會順便檢查期別是否相符，不符會丟 `PeriodMismatchError`。
中獎號碼不會自動下載（財政部 API 需要申請 AppID），請自行輸入或存成 JSON 用 `WinningNumbers.from_json()` 載入。

### 指令列

```bash
einvoice-tw parse "<左QR>" "<右QR>"           # 易讀摘要
einvoice-tw parse --json "<左QR>"             # JSON
einvoice-tw parse --csv < 掃描紀錄.txt > 品項.csv   # 一次轉很多張，每個品項一列
einvoice-tw check AB11223344 --period 11306 --special ... --grand ... --first ... ... ...
einvoice-tw check --numbers 11306.json < 掃描紀錄.txt
einvoice-tw ubn 04595257 12345678             # 統一編號檢查碼（含 2023 年新規則）
```

`stdin` 一行一個掃描結果，以 `**` 開頭的行會自動接到上一行的發票。

### 特色

- 完整解析 77 碼固定表頭、營業人自行使用區、品目筆數與中文編碼參數。
- 支援 Big5、UTF-8（含境外電商的 `3`）、Base64 三種編碼；Base64 不論左右各自編碼或整段切開都能還原。
- 容錯：左右 QR 順序顛倒、小寫字軌、自行使用區長度不足、右 QR 缺 `**`、Base64 損壞；`--strict` 可改為嚴格模式。
- 自動修復掃描器把 Big5 / UTF-8 當成 Latin-1 解出來的亂碼。
- 對獎涵蓋特別獎、特獎、頭獎、二獎至六獎（末 7 至 3 碼）、增開六獎，並檢查期別。

MIT 授權。問題回報與建議：https://github.com/Aaron-lab-c/einvoice-tw/issues
