206 lines
6.8 KiB
Python
206 lines
6.8 KiB
Python
#!/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()
|