Zum Hauptinhalt springen

Kunden verwalten

[admin] [billing]

Kundenverwaltung im Admin: Einladen, Freischalten, Pakete zuweisen, Vertragsdaten, Kündigung, Löschung. Inkl. DSGVO-konformes Archiv.

Für wen​

Admins, die ihre Lizenz-Kunden manuell oder per Self-Service-Link aufnehmen, Pakete zuordnen, Verträge verwalten.

Übersicht​

Sidebar → Kunden: Liste aller Kunden. Spalten: Kundennummer, Name/Firma, Paket, Status, Erstellt. Zwei Filter-Reihen:

  • Status-Filter: alle / aktiv / pending / wartet auf Zahlung / pausiert / inaktiv / gekündigt.
  • Branchen-Filter (NACE-Sektionen): zeigt nur Kunden, deren Branche in einer der gewählten NACE-Sektionen liegt (Mehrfachauswahl, ODER). Leer = alle. Das Kunden-Detail zeigt die zugeordnete Branche jedes Kunden.

Status-Lifecycle​

  • pending — eingeladen / registriert, noch nicht freigeschaltet
  • pending_payment (wartet auf Zahlung) — vom Admin freigeschaltet: Es wurde eine Rechnung gestellt und auf Zahlung gewartet. Wird automatisch active, sobald die Zahlung eingeht (invoice.paid).
  • active — freigeschaltet und bezahlt, hat Portal-Zugang
  • paused — vorübergehend ausgesetzt (reaktivierbar)
  • inactive — deaktiviert
  • cancelled — Abonnement gekündigt (Enddatum erreicht / gesetzt)
  • Gelöscht (deleted_customers-Archiv): DSGVO-konform anonymisiert

Schritt für Schritt — Kunden einladen​

1. + Kunde einladen​

Kunden → + Kunde einladen.

2. Felder​

  • E-Mail (Pflicht) — Empfänger der Einladungs-Mail
  • Vorname / Nachname / Firma
  • Paket auswählen (Dropdown aller aktiven Pakete des Anbieter)
  • Add-ons optional zuweisen
  • Notizen (intern, für dich)

3. Versenden​

licensio generiert einen Registrierungs-Token, schickt eine Mail mit dem Link /<tenant>.licensio.io/register/<token>. Der Kunde klickt den Link und setzt ein Passwort — der Account wird mit Status pending angelegt.

4. Freischalten (immer per Rechnung)​

Du schaltest den Kunden manuell im Kunden-Detail frei (Sektion Status → Freischalten). Die Freischaltung eines kostenpflichtigen, buchbaren Pakets erzeugt IMMER eine Stripe-Rechnung (send_invoice, 14 Tage Fälligkeit) im Connected-Account des Anbieter — unabhängig davon, ob ein Zahlungsmittel hinterlegt ist. Auf dem Admin-Pfad gibt es keinen Checkout (Checkout nur beim Self-Service /join). Der Kunde geht in den Status pending_payment und erhält die Rechnungs-Mail mit Hosted-Payment-Link. Der Status wechselt erst dann automatisch auf active, wenn invoice.paid eingeht.

  • Zahlungslink erneut senden: Ist die Rechnung/Subscription abgelaufen oder storniert („totes" Abo), zeigt das Kunden-Detail einen Button „Zahlungslink erneut senden", der eine frische Rechnung erzeugt.
  • Kostenlos / kein Paket: Ein Paket mit Preis 0 (oder kein Paket) wird direkt aktiviert, ohne Rechnung.
  • Ein zukünftiger Vertragsstart wird im Rechnungs-Flow nicht abgebildet (mit Hinweis geblockt).
  • Self-Service-Auto-Freischaltung: Ein Paket mit auto_activate=true schaltet den Kunden bei der Self-Service-Registrierung über /join direkt frei (dieser Pfad nutzt Checkout und Sofortzahlung) — ohne manuelle Freischaltung.

Kunden-Detail​

Klick auf Kunde in der Liste. Das Layout besteht aus einer fixen Kopf-Kachel und aufklappbaren Sektionen (Collapsibles). Der Auf-/Zu-Zustand jeder Sektion und der aktive Logs-Tab werden pro Kunde im Browser gespeichert (localStorage). Standardmäßig sind alle Collapsibles zugeklappt.

Fixe Kachel "Kontakt & Abonnement"​

Immer geöffnet, oben. Bündelt Stammdaten + Abonnement. Über den Bearbeiten-Button (oben rechts) werden die Stammdaten inline editierbar (Felder werden zu Eingaben, Speichern / Abbrechen):

  • Name, Firma, E-Mail, Kundennummer (customer_number, z.B. PT-2026-0001), Status-Badge, externe ID
  • Adresse (Straße, PLZ, Stadt, Land), USt-ID, Steuernummer
  • Ansprechpartner, Zahlungsart, interne Nr. (customer_number_int, sekundär)
  • Branche (NACE): die dem Kunden zugeordneten NACE-Rev.-2.1-Abteilungen (bis zu 3, die erste ist die Hauptbranche), als lesbare Labels (customers.industry_codes, Migration 093)
  • Account-Typ: Whitelabel (Default) oder Connect, abgeleitet aus customers.connect_user_id (NULL = Whitelabel, gesetzt = Connect)
  • Zahlungsstatus (Stripe-Subscription live: Status, Rechnungsstatus wie „bezahlt" / „offen bis <Datum>" / „storniert", Betrag, nächste/letzte Belastung, Link zur Hosted-Rechnung)
  • Rechnungen: die Stripe-Rechnungen des Abos (Nummer, Betrag, Status, Link) — so sind Proration-Rechnungen für Zusatzpakete ohne Stripe-Login einsehbar
  • Pakete & Zahlung: Basis-Paket (wechselbar) + Add-ons, Add-Package-Button (Payment-Link erzeugen / per Mail senden)

Bearbeiten (Admin): PATCH /api/customers/[id] schreibt first_name, last_name, company, responsible_person, street, zip, city, country, vat_id, tax_number, payment_method. Best-effort Stripe-Sync wie im Portal-Self-Edit: Adresse + Name auf den Stripe-Customer, vat_id als eu_vat-Tax-ID. tax_number bleibt nur in der DB (Stripe kennt keine nationale Steuernummer). country wird als 2-stelliger ISO-Code validiert. E-Mail bleibt ausgenommen (eigener OTP-Flow via „E-Mail ändern").

Aufklappbare Sektionen (Standard: zugeklappt)​

  • Logs — Tab-Bar mit drei Tabs (jeweils letzte 10):
    • Datei-Downloads — aus download_logs (Datum, Produkt, Dateityp)
    • Logins — aus customer_activity (activity_type='login', Datum + gekürzter IP-Hash)
    • Feed-Exporte — aus customer_activity (activity_type='feed_export', Datum + Format + via token/session)
    • Leerer Tab zeigt „Noch keine Daten". Tab-Auswahl wird pro Kunde gemerkt.
  • Support-Verlauf — letzte 3 Tickets (support_tickets, sortiert nach updated_at, mit Status-Badge + Link zur Ticket-Detailseite) + E-Mail-Verlauf (email_logs)
  • Einwilligungen — DSGVO-Nachweis (customer_consents, nur wenn vorhanden)
  • Dokumente — Vertrags-Uploads (customer_documents) + Vertragsstart-Datum
  • Interne Notizen
  • Status — Freischalten (pending → pending_payment per Rechnung → active bei invoice.paid; „Zahlungslink erneut senden" solange pending_payment) / Deaktivieren / Aktivierung ablehnen (rejected_at, rejection_reason) / Pausieren-Reaktivieren. Kündigung ist hier integriert (nur bei status='active'): „Abonnement kündigen" (Datum-Modal) bzw. Kündigung zurücknehmen.
  • Aktionen — Registrierungslink neu senden, Passwort-Reset, Stripe-Billing-Portal öffnen
  • Gefahrenzone — Löschen (DSGVO Art. 17): Kunde wird in deleted_customers archiviert (nur customer_number, tenant_id, deleted_at, deleted_by — KEIN PII), Auth-User + Customer-Record werden gelöscht.

Hinweis: Logins + Feed-Exporte stammen aus dem Customer-Activity-Logging (Migration 060). Downloads bleiben separat in download_logs. Account-Typ-Anker customers.connect_user_id (Migration 061, nullable, noch ohne FK) — licensio Connect ist Phase 2.

Kundennummern​

Auto-Trigger generiert: PT-2026-0001, PT-2026-0002, ... pro Anbieter + Jahr. Nach Kunden-Löschung wird die Nummer NICHT wiederverwendet — sie bleibt im deleted_customers-Archiv reserviert (Migration 013).

Häufige Fragen​

Wie lange dauert die Stripe-Synchronisation? Sofort beim Speichern. Bei Stripe-Express-Accounts werden manche Felder (z.B. company.address) nicht akzeptiert — dann silent best-effort, der Rest wird gespeichert.

Woher kommt die Kundenadresse? Beim Self-Service-Checkout unter /join fragt Stripe eine Pflicht-Adresse (und optional die USt-ID) ab. Nach Abschluss übernimmt der Webhook (checkout.session.completed) Straße/PLZ/Stadt + USt-ID automatisch in den Kundendatensatz — aber nur, wenn die Felder noch leer sind. Der Admin-Freischaltungs-Pfad erzeugt eine send_invoice-Rechnung (kein Checkout) — dort wird die Adresse nicht automatisch erfasst; pflege sie im Admin oder lass sie den Kunden im Portal eintragen. Eine später im Portal oder Admin gepflegte Adresse wird nie überschrieben. country wird nicht aus dem Checkout übernommen (bleibt beim Registrierungs-Wert). Hinweis: Der Setup-Checkout (Pakete ohne sofortige Abbuchung) sammelt keine Adresse — dort bleibt das Feld leer, bis es im Portal/Admin gepflegt wird. Bestandskunden lassen sich per Backfill-Script (apps/portal/scripts/backfill-stripe-addresses.ts) nachziehen.

Was wenn ein Kunde sich selbst registriert hat? Self-Service-Pakete erstellen den Customer mit status='pending'. Du siehst sie in der Liste mit Filter "Pending".

Kann ich Kunden CSV-importieren? Phase 2 — Bulk-Import noch nicht implementiert (product_imports-Tabelle ist Skeleton dafür).

DSGVO-Auskunft für einen Kunden? Aktuell manuell aus DB exportierbar. Self-Service-Auskunft (Phase 2 / nice-to-have).

Was wenn der Stripe-Customer-Update fehlschlägt? licensio-Customer wird trotzdem gespeichert. Du siehst eine Warnung im UI. Stripe-Sync kannst du via "Erneut synchronisieren"-Button wiederholen.

Verwandt​