minimal_vietqr.py
Code
# minimal_vietqr.py
# Yêu cầu: pip install segno
import segno
# helper TLV (id là string 2 ký tự, value là string ASCII)
def tlv(id_str: str, value: str) -> str:
length = f"{len(value):02d}"
return f"{id_str}{length}{value}"
# CRC16-CCITT (CRC-CCITT-FALSE): poly 0x1021, init 0xFFFF
def crc16_ccitt_false(data: bytes) -> int:
crc = 0xFFFF
for b in data:
crc ^= (b << 8)
for _ in range(8):
if (crc & 0x8000):
crc = ((crc << 1) & 0xFFFF) ^ 0x1021
else:
crc = (crc << 1) & 0xFFFF
return crc & 0xFFFF
# build payload minimal (ví dụ account transfer dạng VietQR)
def build_minimal_vietqr(bin_id: str, consumer_id: str, amount: str = None, memo: str = None):
parts = []
# 00 Payload Format Indicator (01)
parts.append(tlv("00", "01"))
# 01 Point of initiation method: "12" dynamic, "11" static. (ví dụ static)
parts.append(tlv("01", "11"))
# Merchant Account Information (ID "26" theo EMV) - subfields:
# sub 00: GUI (NAPAS AID) -> A000000727 (common for NAPAS) + maybe 0124...
# sub 01/02: BIN ID / consumer info depends on spec. Đây là ví dụ đơn giản:
mai = ""
# subtag 00 = GUI
mai += tlv("00", "A000000727") # NOTE: kiểm tra AID chính xác theo spec NAPAS
# subtag 01 = BIN ID (ví dụ)
mai += tlv("01", bin_id)
# subtag 02 = consumer id (tài khoản)
mai += tlv("02", consumer_id)
parts.append(tlv("26", mai))
# 52 Merchant Category Code (dummy) - 0000 nếu không có
parts.append(tlv("52", "0000"))
# 53 Currency (704 = VND)
parts.append(tlv("53", "704"))
# 54 Transaction amount (optional)
if amount:
parts.append(tlv("54", amount))
# 58 Country code
parts.append(tlv("58", "VN"))
# 59 Merchant name (tối đa 25) - test
parts.append(tlv("59", "TEN_MERCHANT"))
# 60 Merchant city
parts.append(tlv("60", "HANOI"))
# 62 Additional data field templates (ví dụ bill number)
if memo:
add_data = tlv("01", memo) # subfield 01 = bill/reference (ví dụ)
parts.append(tlv("62", add_data))
# assemble (tính CRC sau khi thêm field 63 with length 04 và placeholder)
payload_without_crc = "".join(parts)
# add CRC ID 63 and length 04 and placeholder (value will be computed)
payload_for_crc = payload_without_crc + "63" + "04" # CRC value to be appended as 4 hex chars (uppercase)
# compute CRC on ASCII bytes
crc = crc16_ccitt_false(payload_for_crc.encode('utf-8'))
crc_hex = f"{crc:04X}"
full_payload = payload_for_crc + crc_hex
return full_payload
if __name__ == "__main__":
bin_id = "970436"
consumer_id = "1031933430"
amount = "15000" # VND as string (format depends spec)
memo = "Thanh toan hoa don 123"
emv_payload = build_minimal_vietqr(bin_id, consumer_id, amount, memo)
print("EMV payload:", emv_payload)
# tạo ảnh QR (segno)
qr = segno.make(emv_payload)
qr.save("vietqr_minimal.png", scale=4)
print("Lưu file vietqr_minimal.png")