# Finanzen: Stripe-Rechnungen und Finom-Konto im Ticketsystem Stand: 2026-10-01 · Status: Entwurf zur Freigabe Grundlage: Recherche `/root/reports/Finanzübersicht für kleine Webagentur.md` (Notizen in `/root/research_notes/Finanzübersicht für kleine Webagentur/`) und die Machbarkeitsprüfung vom 30.09./01.10.2026 (Enable Banking produktiv getestet: Finom-Konto, Salden, Umsätze). ## Ziel Die Seite „Finanzen“ im Ticketsystem (WOMS, `ticket.webklar.com/finance`) zeigt nicht mehr InvoiceNinja, sondern **Stripe-Rechnungen** und das **Finom-Geschäftskonto**. Sie beantwortet beim wöchentlichen Blick drei Fragen: Wie viel Geld ist wirklich frei? Was muss ich heute tun? Reicht das Geld die nächsten 30 Tage? Projektrechnungen werden direkt aus WOMS über Stripe geschrieben und verschickt. Erfolg: Kein Zahlungseingang bleibt unzugeordnet, keine überfällige Rechnung bleibt unbemerkt, und der Bankzugang läuft nie unbemerkt ab. ## Feststehende Rahmenbedingungen (geprüft) - **WEBklar GbR, Kleinunternehmer nach § 19 UStG** (Impressum, Stripe-Rechnungsfuß im Portal). Daraus folgt: keine Umsatzsteuer auf Rechnungen, keine USt-Voranmeldung, keine USt-Rücklage, **keine E-Rechnungspflicht beim Ausstellen** – das Stripe-PDF genügt. Einkommensteuer zahlen die Gesellschafter privat; Gewerbesteuer erst über 24.500 € Gewinn. - **Bank:** Enable Banking, App „WEBklar Finanzen“, ID `b2582270-11bf-4489-affb-1a151f076c07`, Produktion, nur Kontoinformationen (AIS). Schlüssel auf dem Server erzeugt (`/home/webklar/apps/ticket-integrations/enablebanking.pem`, verlässt den Server nie). Sitzung zum Finom-Konto „Webklar“ besteht, gültig bis **29.03.2027**; Sitzungs-ID und Konto-UID in `/home/webklar/apps/ticket-integrations/enablebanking-session.txt`. Finom liefert bei Eingängen Zahlername, IBAN und Verwendungszweck; Kartenzahlungen haben eine Händlerkategorie (MCC). PSD2-Grenzen: etwa 4 automatische Abrufe pro Tag, Umsätze nur der letzten ~90 Tage, Zugang höchstens 180 Tage. - **Enable Banking ohne Vertrag ist nur zur Erprobung erlaubt** („solely for evaluation purposes or for the personal use of private individuals“, Terms → Restriction of Use). Für den Dauerbetrieb braucht es einen Vertrag (support.api@enablebanking.com). Bauen und testen ist gedeckt. - **Stripe** ist im Kundenportal eingerichtet (Live-Schlüssel, Abos per Karte und SEPA-Lastschrift, Rechnungsfuß § 19 UStG). Bisher hat noch keine Stripe-Auszahlung das Finom-Konto erreicht. - **InvoiceNinja bleibt installiert** und unverändert in Betrieb, verschwindet aber aus der WOMS-Oberfläche. Einzige relevante Altrechnung: 0005 (EWV Software GmbH, 168,79 €), am 05.08.2026 per Überweisung bezahlt und in InvoiceNinja seit 01.10.2026 als bezahlt markiert. - Die Kundenstammdaten (`customers` in `woms-database`) teilen sich WOMS und Portal; Stripe-Kunden hängen über `metadata.webklarCustomerId` daran (`getOrCreateStripeCustomer` im Portal). ## Entscheidungen (Standardwerte zur Freigabe) 1. **Backend im Kundenportal-Server** unter `/api/admin/finanzen/*`, geschützt mit `requireAppwriteStaff` (Appwrite-JWT, Label `admin`, Sitzung mit zweitem Faktor). Dort liegen schon Stripe, Jobs, Mails, Appwrite-Setup-Skripte und Tests. `ticket-integrations` bleibt unverändert. 2. **Nur Admins** sehen „Finanzen“ (Menüpunkt und Seiten). Justin braucht als Gesellschafter das Label `admin` (Prüfung beim Livegang). 3. **Schlüssel** wandert nach `/home/webklar/apps/secrets/enablebanking/` und wird schreibgeschützt in den Portal-Container eingebunden. Die Sitzungs-ID steht in Appwrite (ohne Schlüssel wertlos). 4. **Bankdaten werden dauerhaft gespeichert** (Collection `bankTransactions`), weil die Bank nur 90 Tage liefert. Abruf automatisch alle 6 Stunden (4× täglich) und per Knopf „Aktualisieren“ (höchstens alle 10 Minuten; mit PSU-Kopfzeilen, damit es nicht auf das Tageslimit zählt). 5. **Bankzugang erneuern** direkt aus WOMS: Knopf → Finom-Freigabe → Rückkehr auf `https://ticket.webklar.com/finanzen/bank-callback` (bereits als Redirect-URL registriert). Hinweis in „Zu erledigen“ ab 30 Tagen vor Ablauf, Team-Mail (Vorlage T18) bei 30, 14 und 3 Tagen. 6. **Projektrechnungen** entstehen in WOMS über Stripe: Kunde wählen, Positionen (Beschreibung, Menge, Einzelpreis), Zahlungsziel 14 Tage, Versand per Stripe-Mail mit PDF und Zahlungsseite. Zahlarten stellt der Nutzer im Stripe-Dashboard ein (Empfehlung: Karte und SEPA-Lastschrift). Im Rechnungsfuß stehen der § 19-Hinweis und die Bankverbindung mit der Bitte, die Rechnungsnummer als Verwendungszweck anzugeben. Entwürfe lassen sich löschen, versendete Rechnungen nur stornieren. 7. **Zahlungsabgleich als Vorschlag mit Bestätigung**, nie still automatisch: Eine Bestätigung markiert die Stripe-Rechnung „außerhalb von Stripe bezahlt“ (`paid_out_of_band`, nicht umkehrbar) und merkt sich die Bankbuchung. Stripe-Auszahlungen werden der Auszahlung zugeordnet (Umbuchung, kein Umsatz). Teilzahlungen werden nicht unterstützt (Hinweis: in Stripe direkt erfassen). 8. **Fixkosten** pflegt der Nutzer als Liste (Name, Betrag, Rhythmus, nächstes Datum, Suchbegriff für die Gegenpartei). Erkannte wiederkehrende Ausgaben werden nur vorgeschlagen (mindestens 3 Buchungen). 9. **Vorschau 30 Tage** mit zwei Linien: „nur sicher“ (Kontostand, unterwegs befindliche Stripe-Auszahlungen, Fixkosten, ESt-Vorauszahlungen) und „erwartet“ (zusätzlich offene Rechnungen und Abo-Verlängerungen). Kein Konfidenzkegel, eine Annahmezeile in Klartext. 10. **Steuer:** Rücklage = einstellbarer Prozentsatz (Standard 30 %) vom Gewinn des laufenden Jahres (Einnahmen minus Ausgaben laut Bank). Kleinunternehmer-Zähler nach Zahlungseingang: laufendes Jahr gegen 25.000 € (Status im Folgejahr) und 100.000 € (harte Grenze), Vorjahr als Einstellung. ESt-Vorauszahlungen als optionale Einstellung (Betrag je Quartal, fällig 10.3./10.6./10.9./10.12.). 11. **Kundenansicht:** Der Kunden-Tab „Rechnungen“ zeigt Stripe-Rechnungen des Kunden und „Rechnung schreiben“. Der Ticket-Tab „Rechnungen“ entfällt. 12. **Nicht im Umfang:** Kundenportal-Tab „Rechnungen“ (zeigt weiter InvoiceNinja, eigener Folgeschritt), Stundenguthaben je Kunde, Projektmarge, Kundenkonzentration, Angebote, Teilzahlungen, E-Rechnung, Ausgabenbelege, mehrere Bankkonten. ## Seitenaufbau (WOMS `/finance`) Reihenfolge von oben nach unten; Blöcke 0–2 stehen im ersten Bildschirm. Beträge in de-DE (`1.234,50 €`, rechtsbündig, `tabular-nums`), Daten `01.10.2026`, Farbe nie allein als Signal. | Nr. | Block | Inhalt | |---|---|---| | 0 | Statuszeile | „Bank: Stand vor 3 Std. · jüngste Buchung 29.09. · Zugang bis 29.03.2027“, „Stripe: Stand …“; bei Fehler „Abruf fehlgeschlagen: …“; Knopf „Aktualisieren“ | | 1 | Liquiditätskopf | Kontostand (gebucht), „Verfügbar nach Rücklagen“ (Kontostand − Steuer-Rücklage − Fixkosten der nächsten 30 Tage − Mindestpuffer; negativ erlaubt; Aufschlüsselung per Klick), „Tiefster Stand in 30 Tagen“ (nur sicher / erwartet, jeweils mit Datum) | | 2 | Zu erledigen | Höchstens 7 Zeilen, je mit genau einer Aktion; Leerzustand „Nichts offen“ | | 3 | Vorschau 30 Tage | Tabelle nach Datum: Posten, sicher/erwartet, Betrag, laufender Saldo beider Linien; Annahmezeile | | 4 | Steuer | Rücklage (Betrag, Prozentsatz, Gewinn lfd. Jahr), Kleinunternehmer-Zähler, nächste ESt-Vorauszahlung (falls eingestellt) | | 5 | Abos | Monatliche Abo-Einnahmen (aktive + `past_due`, Jahrespreise ÷ 12), Anzahl zahlender Kunden, gefährdete Einnahmen (`past_due`), Liste der Abos | | 6 | Rechnungen | Filter Offen / Überfällig / Bezahlt / Entwürfe / Alle; Altersgruppen (nicht fällig, 1–30, 31–60, über 60 Tage) mit Summe und Anzahl; Tabelle Nr., Kunde, Datum, Fällig, Betrag, Offen, Status, Aktionen (PDF, Zahlungsseite, Senden, Stornieren, Löschen); Knopf „Rechnung schreiben“ | | 7 | Buchungen | Alle gespeicherten Buchungen, neueste zuerst, 50 je Seite: Datum, Gegenpartei + Verwendungszweck, Betrag mit Vorzeichen, Zuordnung (Rechnung, Stripe-Auszahlung, Fixkosten, Einnahme ohne Stripe-Rechnung, Privat/Umbuchung, Sonstiges, offen) | Unterseite `/finance/einstellungen`: Mindestpuffer, Rücklage-Prozentsatz, Vorjahresumsatz, ESt-Vorauszahlung je Quartal, Bankverbindungstext für den Rechnungsfuß, Fixkostenliste, Bankzugang (Status, „Neu verbinden“). Mobil: Blöcke gestapelt in derselben Reihenfolge, Tabellen als zweizeilige Zeilen. ### „Zu erledigen“ – Arten | Art | Wann | Aktion | Dringlichkeit | |---|---|---|---| | `bank-zugang` | Zugang läuft in ≤ 30 Tagen ab oder ist abgelaufen | Neu verbinden | hoch ab ≤ 14 Tagen | | `bank-abruf` | Letzter erfolgreicher Abruf älter als 24 h | Aktualisieren | mittel | | `ueberfaellig` | Offene Rechnung, Fälligkeit < heute | Zahlungsseite öffnen / Erinnerung über Stripe senden | hoch ab 30 Tagen (Verzug) | | `zuordnung` | Eingang (Betrag > 0) ohne Zuordnung | Zuordnen (mit Vorschlag, falls vorhanden) | mittel | | `abo-zahlung` | Abo im Status `past_due` oder `unpaid` | Kunde öffnen | hoch | | `entwurf` | Rechnungsentwurf älter als 3 Tage | Öffnen | info | | `fixkosten-vorschlag` | Wiederkehrende Ausgabe erkannt, nicht in der Fixkostenliste | Übernehmen | info | | `kleinunternehmer` | Einnahmen lfd. Jahr ≥ 70 % von 25.000 € | Einstellungen | mittel, hoch ab 100 % | Sortierung: hoch vor mittel vor info, innerhalb nach Datum (älteste zuerst). ## Datenmodell (Appwrite, `woms-database`) - `bankConnections` – ein Dokument `finom`: `status` (`aktiv` | `abgelaufen` | `fehler`), `sessionId`, `accountUid`, `accountIdHash`, `ibanMasked`, `iban`, `validUntil` (ISO), `lastSyncAt`, `lastSyncOk` (bool), `lastError`, `lastManualSyncAt`, `balanceBookedCents`, `balanceAvailableCents`, `balanceDate`, `latestBookingDate`, `authState`, `authStartedAt`, `warnedThresholds` (z. B. `30,14`). - `bankTransactions` – Dokument-ID = erste 36 Zeichen des SHA-256 aus `entry_reference` (sonst `transaction_id`, sonst Datum|Betrag|Gegenpartei|Verwendungszweck): `accountUid`, `status` (`BOOK`/`PDNG`), `bookingDate`, `valueDate` (`YYYY-MM-DD`), `amountCents` (Integer, Eingang +, Ausgang −), `currency`, `counterpartyName`, `counterpartyIban`, `remittance`, `mcc`, `entryReference`, `category` (leer | `rechnung` | `rechnung-extern` | `stripe-auszahlung` | `fixkosten` | `privat` | `sonstiges`), `matchedInvoiceId`, `matchedInvoiceNumber`, `matchedPayoutId`, `fixedCostId`, `note`, `syncedAt`. - `financeSettings` – ein Dokument `main`: `minBufferCents` (0), `taxReservePercent` (30), `previousYearRevenueCents` (leer), `estPrepaymentCents` (0), `bankDetailsText`. - `financeFixedCosts`: `name`, `amountCents` (positiv), `interval` (`monat` | `quartal` | `jahr`), `nextDate`, `counterpartyMatch`, `active`. Stripe-Rechnungen, -Auszahlungen und Abos werden **nicht** gespiegelt: Rechnungen und Auszahlungen liest der Server live (Zwischenspeicher 60 s, nach eigenen Änderungen geleert), Abos kommen aus der vorhandenen Collection `subscriptions`. ## Schnittstellen (`/api/admin/finanzen`) | Methode, Pfad | Zweck | |---|---| | GET `/uebersicht` | Alles für Blöcke 0–6 in einer Antwort (Status, Kopf, Aufgaben, Vorschau, Steuer, Abos, Rechnungs-Altersgruppen) | | GET `/rechnungen?status=&kunde=` | Stripe-Rechnungen (status: `offen`, `ueberfaellig`, `bezahlt`, `entwurf`, `alle`) | | POST `/rechnungen` | Rechnung anlegen (`{ customerId, items, daysUntilDue, memo, senden }`) | | POST `/rechnungen/:id/senden` | Entwurf festschreiben und senden | | POST `/rechnungen/:id/stornieren` | Offene Rechnung stornieren | | DELETE `/rechnungen/:id` | Entwurf löschen | | GET `/buchungen` | Gespeicherte Buchungen (höchstens 1000, neueste zuerst; die Oberfläche blättert in 50ern) mit Zuordnungsvorschlägen für offene Eingänge | | POST `/buchungen/:id/zuordnen` | `{ invoiceId }` oder `{ payoutId }` oder `{ fixedCostId }` oder `{ category }` | | DELETE `/buchungen/:id/zuordnung` | Zuordnung lösen (nicht bei Rechnungen, die schon als bezahlt markiert sind) | | POST `/bank/aktualisieren` | Sofortabruf | | POST `/bank/verbinden` | Freigabe starten → `{ url }` | | POST `/bank/rueckkehr` | `{ code, state }` oder `{ error, state }` → neue Sitzung | | GET, PATCH `/einstellungen` | Einstellungen | | GET, POST `/fixkosten`; PATCH, DELETE `/fixkosten/:id` | Fixkostenliste | ## Zuordnungsregeln (Vorschläge, höchstens 3 je Buchung) 1. **Stripe-Auszahlung (hoch):** Gegenpartei oder Verwendungszweck enthält „stripe“, Betrag = Betrag einer Auszahlung, Buchungsdatum höchstens 3 Tage von `arrival_date` entfernt. 2. **Rechnungsnummer (hoch / mittel):** normalisierter Verwendungszweck (Großbuchstaben, ohne Leerzeichen und Bindestriche) enthält die normalisierte Stripe-Rechnungsnummer. Betrag = offener Betrag → hoch, sonst mittel mit Hinweis „Betrag weicht ab“. 3. **Betrag und Name (mittel):** Betrag = offener Betrag und ein Wort (≥ 4 Zeichen, ohne GmbH, UG, GbR, AG, KG, e.K., und, der, die) aus Firmen- oder Kundenname kommt im Zahlernamen vor. 4. **Nur Betrag (niedrig):** Betrag = offener Betrag genau einer offenen Rechnung. Bestätigen mit Rechnung ist nur möglich, wenn der Betrag dem offenen Betrag entspricht. ## Fehlerverhalten - Bankabruf scheitert: alte Daten bleiben sichtbar und werden als veraltet markiert; `lastError` in der Statuszeile; kein Datenverlust. - Zugang abgelaufen (Enable Banking 401/403 oder Sitzung `EXPIRED`): Statuszeile + Aufgabe `bank-zugang` (hoch), Abrufe pausieren bis zur Neuverbindung. - Stripe nicht erreichbar: Übersicht liefert Bankteil trotzdem; Stripe-Blöcke zeigen „Stripe nicht erreichbar“. - Rückkehr mit falschem `state` oder Fehler von Finom: Meldung auf der Rückkehrseite, bestehende Sitzung bleibt gültig. ## Livegang (Reihenfolge) 1. Schlüssel nach `secrets/enablebanking/` verschieben, Compose: Mount und Umgebungsvariablen `ENABLE_BANKING_APP_ID`, `ENABLE_BANKING_KEY_PATH`. 2. Setup-Skript legt Collections an und übernimmt die bestehende Sitzung (kein neuer Finom-Login). 3. Portal `main` pushen (Nutzer-OK), Container neu starten, erster Abruf prüfen. 4. Stripe-Dashboard: Rechnungsnummern fortlaufend auf Kontoebene, Zahlarten für Rechnungen (Karte, SEPA-Lastschrift) – Nutzer. 5. WOMS `test` pushen (Nutzer-OK). 6. Buchung „Rechnung 0005“ als „Einnahme ohne Stripe-Rechnung“ zuordnen. 7. Vor dem Dauerbetrieb: Vertrag mit Enable Banking (Nutzer).