# Swiss QR-Bill - Entwickler-Anleitung ## Offizielle Spezifikation Die **einzige verbindliche Quelle** ist die SIX Group Spezifikation: **Swiss Implementation Guidelines QR-Rechnung v2.3** https://www.six-group.com/dam/download/banking-services/standardization/qr-bill/ig-qr-bill-v2.3-en.pdf Dieses Dokument ist Pflichtlektüre - es definiert alle Regeln. --- ## Wichtigste Regeln ### 1. IBAN-Typen und Referenzen | Referenztyp | IBAN-Typ | IID (Stelle 5-9) | Prüfziffer | |-------------|----------|------------------|------------| | **QRR** | QR-IBAN | 30000-31999 | Modulo 10 rekursiv | | **SCOR** | Normale IBAN | < 30000 oder > 31999 | Modulo 97 (ISO 11649) | | **NON** | Normale IBAN | < 30000 oder > 31999 | Keine | **Wichtig:** QRR-Referenz funktioniert **nur** mit QR-IBAN. SCOR/NON **nur** mit normaler IBAN. ### 2. QRR-Referenz (27 Stellen) ``` 26 Ziffern + 1 Prüfziffer = 27 Stellen Prüfziffer-Berechnung (Modulo 10 rekursiv): - Tabelle: [0, 9, 4, 6, 8, 2, 7, 1, 3, 5] - Für jede Ziffer: carry = tabelle[(carry + ziffer) % 10] - Prüfziffer = (10 - carry) % 10 ``` **JavaScript-Beispiel:** ```javascript function calculateMod10Recursive(ref) { const table = [0, 9, 4, 6, 8, 2, 7, 1, 3, 5]; let carry = 0; for (const char of ref) { const digit = parseInt(char, 10); if (!isNaN(digit)) { carry = table[(carry + digit) % 10]; } } return (10 - carry) % 10; } // Verwendung: const reference26 = '00000000000020240115123456'; // 26 Stellen const checkDigit = calculateMod10Recursive(reference26); const fullReference = reference26 + checkDigit; // 27 Stellen ``` ### 3. Strukturierte Adresse (seit Nov. 2025 Pflicht) ```json { "name": "Max 70 Zeichen", "street": "Strassenname (max 70)", "buildingNumber": "Hausnummer (max 16)", "postalCode": "PLZ (max 16)", "city": "Ort (max 35)", "country": "CH" } ``` | Feld | Max. Länge | Pflicht | |------|------------|---------| | name | 70 | Ja | | street | 70 | Nein | | buildingNumber | 16 | Nein | | postalCode | 16 | Ja | | city | 35 | Ja | | country | 2 | Ja (ISO 3166-1 alpha-2) | ### 4. Betrag - Minimum: 0.01 - Maximum: 999'999'999.99 - Maximal 2 Dezimalstellen - Optional (kann leer sein für Spenden etc.) ### 5. Währung Nur **CHF** oder **EUR** erlaubt. --- ## Empfohlene Bibliothek **swissqrbill** (Node.js/TypeScript) https://github.com/schoero/swissqrbill ```bash npm install swissqrbill pdfkit ``` Diese Bibliothek: - Generiert korrektes PDF-Layout nach SIX-Vorgaben - Erstellt den QR-Code - Unterstützt alle Sprachen (DE, FR, IT, EN) - Bietet Optionen für Perforierung/Schere --- ## Beispiel-Payload ```json { "creditor": { "iban": "CH4431999123000889012", "address": { "name": "Meine Firma AG", "street": "Hauptstrasse", "buildingNumber": "1", "postalCode": "8000", "city": "Zürich", "country": "CH" } }, "amount": 1500.00, "currency": "CHF", "reference": { "type": "QRR", "value": "000000000000000000000000000" }, "debtor": { "address": { "name": "Kunde GmbH", "street": "Kundenweg", "buildingNumber": "42", "postalCode": "3000", "city": "Bern", "country": "CH" } }, "additionalInformation": { "message": "Rechnung 2024-001" }, "options": { "language": "de", "separate": true } } ``` --- ## Validierung implementieren ### Checkliste 1. **IBAN-Format prüfen** - CH/LI + 19 alphanumerische Zeichen - Leerzeichen entfernen, Grossbuchstaben 2. **IBAN-Typ erkennen** - IID extrahieren (Stelle 5-9) - 30000-31999 = QR-IBAN - Andere = normale IBAN 3. **Referenz-IBAN-Kompatibilität prüfen** - QRR erfordert QR-IBAN - SCOR/NON erfordert normale IBAN 4. **QRR-Prüfziffer validieren** - Modulo 10 rekursiv (siehe oben) 5. **SCOR-Prüfziffer validieren** - Format: RF + 2 Prüfziffern + max. 21 alphanumerisch - Modulo 97 nach ISO 11649 6. **Adressfelder auf Länge prüfen** - Alle Maximal-Längen einhalten ### IBAN-Validierung (JavaScript) ```javascript function isQrIban(iban) { const cleanIban = iban.replace(/\s/g, '').toUpperCase(); const iid = parseInt(cleanIban.substring(4, 9), 10); return iid >= 30000 && iid <= 31999; } function validateIbanReferenceCombo(iban, referenceType) { const isQr = isQrIban(iban); if (referenceType === 'QRR' && !isQr) { return 'QRR erfordert eine QR-IBAN (IID 30000-31999)'; } if ((referenceType === 'SCOR' || referenceType === 'NON') && isQr) { return 'SCOR/NON erfordert eine normale IBAN'; } return null; // OK } ``` --- ## Sprachen | Code | Sprache | |------|---------| | de | Deutsch | | fr | Französisch | | it | Italienisch | | en | Englisch | Die Sprache beeinflusst: - Überschriften ("Zahlteil" / "Récépissé" / etc.) - Feldbezeichnungen - Zusatztexte --- ## PDF-Layout Der QR-Zahlteil besteht aus zwei Teilen: 1. **Empfangsschein** (links, 62mm breit) 2. **Zahlteil** (rechts, 148mm breit) Gesamtgrösse: 210mm x 105mm (A6 quer, unten auf A4) Die `swissqrbill` Bibliothek generiert das Layout automatisch korrekt. --- ## Testressourcen - **SIX Testdaten:** Im Anhang der offiziellen Spezifikation - **Online-Validator:** https://www.swiss-qr-invoice.org/validator/ - **Style Guide:** https://www.six-group.com/dam/download/banking-services/standardization/qr-bill/style-guide-qr-bill-en.pdf --- ## Häufige Fehler | Fehler | Ursache | Lösung | |--------|---------|--------| | Ungültige Referenz | Falsche Prüfziffer | Modulo 10 rekursiv korrekt berechnen | | IBAN/Referenz Mismatch | QRR mit normaler IBAN | QR-IBAN verwenden oder auf SCOR/NON wechseln | | Adresse zu lang | Feldlänge überschritten | Auf max. Länge kürzen | | Ungültige Währung | USD oder andere | Nur CHF oder EUR verwenden | --- ## API-Endpunkt (dieses Projekt) ``` POST /api/v1/invoice/qr-bill Content-Type: application/json Response: application/pdf (Binary) ``` Health Check: ``` GET /health GET /health/ready ``` > **Hinweis:** Im Docker Health Check `127.0.0.1` statt `localhost` verwenden, da Alpine Linux `localhost` als `[::1]` (IPv6) auflöst, die App aber nur auf IPv4 (`0.0.0.0`) hört. --- ## Repository & Deploy **Repository:** https://git.tradeo.de/mehmed/QR-Invoice.git ### Deploy auf neuem Server ```bash git clone https://git.tradeo.de/mehmed/QR-Invoice.git cd QR-Invoice docker compose up -d ``` Service läuft dann auf Port **3050**.