Files
fio/010qrcodegenerator.py
2025-11-05 19:54:51 +01:00

206 lines
6.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
============================================
QR Platba Generator Fully Documented Version
============================================
This script generates a valid Czech payment QR code (QR Platba)
according to the official ČBA (Czech Banking Association) specification v1.2.
The script creates a correct SPD (Short Payment Descriptor) string,
turns it into a QR code (PNG image), and saves it to disk.
✅ Works with all Czech banks (Fio, ČSOB, KB, Air Bank…)
✅ Supports both IBAN or domestic account format (account/bankcode)
✅ Automatically URL-encodes the message (so spaces and diacritics work)
✅ Can handle optional VS, SS, KS, due date, and instant-payment flag
Usage example (in your terminal):
---------------------------------
python generate_qrplatba.py \
--acc CZ7520100000002800046620 \
--amount 1543.50 \
--vs 7309208104 \
--ss 123456 \
--ks 0308 \
--date 09.12.2025 \
--msg "ahoj zlatíčko" \
--out qr_ok.png
"""
import argparse # For parsing command-line arguments
import urllib.parse # For safe URL-encoding of message text
from pathlib import Path # For easy file handling (cross-platform)
import qrcode # For generating the QR code image
from datetime import datetime # For date parsing and formatting
# --------------------------------------------------------------------
# 🧩 Helper function: Normalize date into the required YYYYMMDD format
# --------------------------------------------------------------------
def normalize_date(date_str: str) -> str:
"""
Converts a user-supplied date string (various formats) into the
standard YYYYMMDD format used by the QR Platba specification.
Acceptable input examples:
"20251209"
"09.12.2025"
"2025-12-09"
"09122025"
Returns:
str: normalized date string in "YYYYMMDD" format
or empty string "" if input is None or empty
"""
if not date_str:
return ""
s = date_str.strip()
# Already in correct 8-digit form?
if len(s) == 8 and s.isdigit():
return s
# Try several common formats
for fmt in ("%d.%m.%Y", "%Y-%m-%d", "%d%m%Y"):
try:
dt = datetime.strptime(s, fmt)
return dt.strftime("%Y%m%d")
except ValueError:
continue
# If all parsing attempts fail → raise an informative error
raise ValueError(
"Date must be in one of formats: YYYYMMDD, DD.MM.YYYY, YYYY-MM-DD, DDMMYYYY"
)
# --------------------------------------------------------------------
# 🔧 Helper function: Build the SPD string (core of QR Platba)
# --------------------------------------------------------------------
def build_spayd(params: dict) -> str:
"""
Assembles a valid SPD (Short Payment Descriptor) string
according to ČBA v1.2 specification.
Example output:
SPD*1.0*ACC:CZ7520100000002800046620*AM:1543.50*CC:CZK*DT:20251209*
X-VS:7309208104*X-SS:123456*X-KS:0308*MSG:ahoj%20zlaticko
"""
# Start with fixed header
parts = ["SPD*1.0*"]
# --- Required field: account number / IBAN ---
acc = params.get("acc", "").strip()
if not acc:
raise ValueError("Missing required parameter: --acc")
parts.append(f"ACC:{acc}*")
# --- Optional: amount ---
amt = params.get("amount")
if amt:
parts.append(f"AM:{float(amt):.2f}*") # always two decimals
# --- Optional: currency (defaults to CZK) ---
cc = params.get("cc") or "CZK"
parts.append(f"CC:{cc}*")
# --- Optional: due date ---
dt = params.get("dt")
if dt:
parts.append(f"DT:{dt}*")
# --- Optional: symbols (VS, SS, KS) ---
for key in ("X-VS", "X-SS", "X-KS"):
val = params.get(key.replace("-", "_").lower())
if val:
parts.append(f"{key}:{val}*")
# --- Optional: payment type (PT:IP = Instant Payment) ---
pt = params.get("pt")
if pt:
parts.append(f"PT:{pt}*")
# --- Optional: message for recipient ---
msg = params.get("msg") or ""
if msg:
# Encode to keep spaces and Czech letters valid
safe_chars = "$%*+-.:/"
encoded_msg = urllib.parse.quote(msg, safe=safe_chars)
parts.append(f"MSG:{encoded_msg}")
# Combine everything into one string
return "".join(parts)
# --------------------------------------------------------------------
# 🚀 Main program entry point
# --------------------------------------------------------------------
def main():
# -------------------------------
# Define all command-line options
# -------------------------------
parser = argparse.ArgumentParser(
description="Generate a Czech QR Platba (payment QR code)."
)
parser.add_argument("--acc", required=True,
help='Account: either "2800046620/2010" or IBAN "CZ..."')
parser.add_argument("--amount", help="Payment amount (e.g. 1543.50)")
parser.add_argument("--vs", help="Variable symbol (X-VS)")
parser.add_argument("--ss", help="Specific symbol (X-SS)")
parser.add_argument("--ks", help="Constant symbol (X-KS)")
parser.add_argument("--date", help="Due date (YYYYMMDD or DD.MM.YYYY etc.)")
parser.add_argument("--msg", help="Message for recipient (will be URL-encoded)")
parser.add_argument("--cc", default="CZK", help="Currency (default CZK)")
parser.add_argument("--pt", help="Payment type (e.g. IP for instant payment)")
parser.add_argument("--out", default="qrplatba.png", help="Output PNG file name")
args = parser.parse_args()
# -------------------------------
# Normalize and prepare arguments
# -------------------------------
dt_norm = normalize_date(args.date) if args.date else ""
params = {
"acc": args.acc,
"amount": args.amount,
"cc": args.cc,
"dt": dt_norm,
"x_vs": args.vs,
"x_ss": args.ss,
"x_ks": args.ks,
"msg": args.msg,
"pt": args.pt,
}
# -------------------------------
# Build the SPD string
# -------------------------------
spayd = build_spayd(params)
# -------------------------------
# Generate QR code image
# -------------------------------
img = qrcode.make(spayd)
output_path = Path(args.out)
img.save(output_path)
# -------------------------------
# Print results for verification
# -------------------------------
print("✅ QR Platba successfully generated")
print("SPD string (you can verify at https://qr-platba.cz/test/):")
print(spayd)
print()
print(f"📂 QR code image saved to: {output_path.resolve()}")
# --------------------------------------------------------------------
# 🔘 Run when executed directly
# --------------------------------------------------------------------
if __name__ == "__main__":
main()