#!/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()