6.1 KiB
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:
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)
{
"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
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
{
"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
-
IBAN-Format prüfen
- CH/LI + 19 alphanumerische Zeichen
- Leerzeichen entfernen, Grossbuchstaben
-
IBAN-Typ erkennen
- IID extrahieren (Stelle 5-9)
- 30000-31999 = QR-IBAN
- Andere = normale IBAN
-
Referenz-IBAN-Kompatibilität prüfen
- QRR erfordert QR-IBAN
- SCOR/NON erfordert normale IBAN
-
QRR-Prüfziffer validieren
- Modulo 10 rekursiv (siehe oben)
-
SCOR-Prüfziffer validieren
- Format: RF + 2 Prüfziffern + max. 21 alphanumerisch
- Modulo 97 nach ISO 11649
-
Adressfelder auf Länge prüfen
- Alle Maximal-Längen einhalten
IBAN-Validierung (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:
- Empfangsschein (links, 62mm breit)
- 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
Repository & Deploy
Repository: https://git.tradeo.de/mehmed/QR-Invoice.git
Deploy auf neuem Server
git clone https://git.tradeo.de/mehmed/QR-Invoice.git
cd QR-Invoice
docker compose up -d
Service läuft dann auf Port 3050.