From 0bb1fe587a5beac379bdb70874f0ee465dc62fd0 Mon Sep 17 00:00:00 2001 From: Flexomatic81 Date: Fri, 27 Feb 2026 16:04:50 +0100 Subject: [PATCH] Add docs --- docs/DEVELOPER-GUIDE.md | 268 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 docs/DEVELOPER-GUIDE.md diff --git a/docs/DEVELOPER-GUIDE.md b/docs/DEVELOPER-GUIDE.md new file mode 100644 index 0000000..ef394c7 --- /dev/null +++ b/docs/DEVELOPER-GUIDE.md @@ -0,0 +1,268 @@ +# 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 +```