MSPercury docs

Stripe-Integration & Kundenabrechnung

Schritt-für-Schritt: vom leeren Workspace zur ersten Stripe-Rechnung, automatisch erstellt aus einem akzeptierten MSPercury-Angebot. Inklusive Behandlung von einmaligen Setup-Pauschalen plus monatlichen / jährlichen Vertragspositionen, USt-IdNr / VAT / EIN / GST-HST und EU-Reverse-Charge.

💡 Wichtig vorweg. MSPercury nutzt nicht Stripe Connect oder „on-behalf-of”-Buchungen. Du verbindest dein eigenes Stripe-Konto mit deinem eigenen API-Key. Geld fließt direkt zwischen dir und deiner Kundschaft — MSPercury sieht nur die API-Antwort, nie das Geld, nie eine Provision, nie einen Markup. Wir sind reiner Datentransporter.

1. Stripe-Konto vorbereiten

Falls du noch kein Stripe-Konto hast: stripe.com/de → Konto anlegen, KYC durchlaufen (Personalausweis + Bankverbindung). Dauert 5–15 Minuten. Bestehendes Konto? Springe direkt zu Schritt 2.

Stripe Tax aktivieren (sehr empfohlen)

Bevor du Rechnungen über MSPercury erstellst, schalte Stripe Tax im Stripe-Dashboard scharf:

  1. Stripe-Dashboard → More → Tax → Settings → Get started
  2. Ursprungs-Adresse setzen (deine Geschäftsadresse, gleich wie in MSPercury → Einstellungen → Workspace)
  3. Steuerregistrierungen hinzufügen — für DE-MSPs: deine deutsche USt-IdNr (DEXXXXXXXXX) + Steuersatz 19 % automatisch
  4. Optional: weitere Länder, in denen du registriert bist (Österreich, Schweiz, …)

🟡 Was Stripe Tax dir abnimmt. Korrekte USt-Berechnung bei innerdeutschen Rechnungen (19 %), automatischer Reverse-Charge bei EU-B2B-Empfängern mit gültiger USt-IdNr (0 %, Annotation auf der Rechnung), Drittland-Rechnungen mit 0 %. Ohne Stripe Tax musst du das alles manuell richten. Stripe Tax kostet 0,5 % pro abgerechneter Transaktion — bei einer 100-€-Rechnung also 50 Cent.

Restricted Key erstellen (statt Secret Key)

Statt deines vollen Secret Keys empfehlen wir dringend einen Restricted Key mit minimalen Rechten. Erstelle ihn so:

  1. Stripe-Dashboard → Developers → API keys → + Create restricted key
  2. Name: MSPercury – Quote-Push (oder ähnlich)
  3. Permissions setzen:
    • Customers: Write
    • Invoices: Write
    • Subscriptions: Write
    • Products: Write
    • Prices: Write
    • Tax IDs (Customers): Write
    • Tax Rates: Read
    • Charges, Payouts, Balance, Reporting: ❌ alles auf None lassen
  4. Speichern → der Key beginnt mit rk_test_… (Test-Modus) oder rk_live_… (Live-Modus)

🔒 Warum ein Restricted Key? Falls dein MSPercury-Workspace jemals kompromittiert wird (ungeschützter Admin-Account, gestohlenes Passwort), kann ein Angreifer mit einem Restricted Key keine Auszahlungen auslösen, keine Refunds verschieben, keine API-Keys rotieren. Er kann nur das tun, was MSPercury ohnehin macht: Rechnungen erstellen. Ein voller Secret Key (sk_live_…) hingegen erlaubt im Wesentlichen alles — vermeide den, wenn möglich.

⚠️ Test-Modus zuerst. Mach dein Onboarding immer mit rk_test_… durch. Stripe-Test-Rechnungen verschicken keine echten E-Mails an deine Kundschaft, ziehen kein echtes Geld ab, schreiben keine echte Buchhaltung mit. Wechsel auf rk_live_… erst, wenn der erste Test-Run sauber durchgelaufen ist.

2. Workspace-Steuerstammdaten setzen

Bevor du den Key in MSPercury einträgst, sind zwei Felder unter Einstellungen → Workspace wichtig:

  • Land (Country): aus dem Dropdown wählen (z. B. Deutschland)
  • Steuer-ID-Typ: füllt sich automatisch wenn du das Land änderst (DE → eu_vat, US → us_ein, CA → ca_gst_hst)
  • USt-IdNr / Steuer-ID: deine eigene DEXXXXXXXXX für deutsche MSPs

💡 Was passiert mit diesen Feldern? Sie erscheinen im Footer jeder PDF (Angebot, Vereinbarung, CheckUp-Bericht), automatisch mit dem im Land üblichen Label: „USt-IdNr.: DEXXX” für deutsche Workspaces, „VAT ID: ATXXX” für österreichische, „EIN: 12-3456789” für US-MSPs, „GST/HST: XXXXXXXXXRT0001” für kanadische. Kein hartcodiertes Deutsch mehr. Außerdem werden sie an Stripe weitergereicht für Stripe-Tax-Jurisdiktions-Auflösung.

3. Stripe-Key in MSPercury eintragen

  1. Einstellungen → Integrationen öffnen
  2. Restricted Key (rk_test_… für den ersten Versuch) ins Feld einfügen
  3. Stripe-Konto verbinden klicken

Was passiert technisch:

  • MSPercury ruft stripe.accounts.retrieve() mit deinem Key auf, um zu prüfen dass der Key gültig ist und Account-Daten zu fetchen
  • Bei Erfolg: Key wird AES-256-GCM verschlüsselt at rest abgelegt (gleicher Helper wie für TOTP-Secrets), Account-Display-Name + ID + Mode (test/live) werden cached
  • Bei Fehler: Stripe-Fehlermeldung wird 1:1 angezeigt (z. B. „No such account” → Key falsch, „Insufficient permissions” → Restricted Key hat zu wenige Scopes)

Die Status-Pille im Settings-Block zeigt dir:

  • 🟢 Verbunden + LIVE-Badge — alles bereit für echte Rechnungen
  • 🟡 Verbunden + TEST-Badge — Sandbox-Modus, keine echten E-Mails / Belastungen
  • Nicht verbunden — kein Key gesetzt

🔑 Key rotieren / ersetzen. Im verbundenen Zustand siehst du einen aufklappbaren „Key ersetzen”-Bereich. Paste neuen Key → der alte wird überschrieben, ein neuer accounts.retrieve()-Call validiert. Wenn dein Restricted Key kompromittiert sein könnte (gestohlenes Notebook, durchgesickertes Repo): direkt ersetzen, dann bei Stripe den alten Key revozieren.

4. Kunden-Steuerstammdaten setzen

Pro Kunde (/customers/[id]/edit):

  • Land: ISO-Code aus dem Dropdown
  • Steuer-ID-Typ: meist auto-gefüllt vom Land (DE→eu_vat, US→us_ein, CA→ca_gst_hst)
  • Steuer-ID-Wert: die tatsächliche Nummer wie DE123456789, 12-3456789, 123456789RT0001
  • Adresse: vollständig mit Straße + PLZ + Stadt

💡 Warum so granular? Ohne Land + Tax-ID-Typ kann Stripe Tax die Jurisdiktion nicht auflösen. Ergebnis: 0 % USt auf Rechnungen, weil Stripe nicht weiß ob es sich um eine inländische Lieferung (19 % MwSt) oder eine EU-Reverse-Charge-Lieferung (0 %, vom Empfänger zu erklären) handelt. Beide sehen aus Stripe-Sicht ohne Land-Info gleich aus.

⚠️ Beim Stripe-Push. MSPercury sucht zuerst per customers.search({ query: 'metadata.mspercury_customer_id:"…"' }) ob der Kunde schon im Stripe-Konto existiert. Falls ja: Adresse + Tax-ID werden synchronisiert. Falls nein: ein neuer Stripe-Customer wird mit tax_id_data und Adresse angelegt. Mehrfaches Pushen erzeugt also keine Duplikate.

5. Angebot bauen und akzeptieren

Schau auf Getting Started wie ein Angebot von Null entsteht. Wichtig für die Stripe-Abrechnung sind die Abrechnungsperioden der Positionen:

MSPercury-PeriodeStripe-MappingWann verschickt
EinmaligInvoice (allein) ODER InvoiceItem auf der ersten Sub-Rechnung (kombiniert)Sofort, einmal
MonatlichSubscription mit interval=monthErste Rechnung sofort, dann jeden Monat
JährlichSubscription mit interval=yearErste Rechnung sofort, dann jährlich

💡 Setze die Periode bewusst. Wenn du eine Setup-Pauschale hast, sollte die als „einmalig” laufen. Eine RMM-Lizenz ist „monatlich”. Eine Backup-Lizenz mit Jahresvertrag ist „jährlich”. Im Angebots-Editor kannst du pro Position frei wählen. Falls du dich vertippst: nach dem Akzeptieren ist Edit-Post-Accept verfügbar (Live-Record + MRR werden synchronisiert; das signierte PDF bleibt als Audit-Referenz unverändert).

Sobald die Kundschaft das Angebot über die Share-Page akzeptiert (oder du es manuell auf akzeptiert setzt), wird der violette Stripe-Block sichtbar.

6. Stripe-Push — was wirklich passiert

Auf der Detailseite eines akzeptierten Angebots erscheint jetzt der violette „Stripe-Rechnung erstellen”-Block. Davor zeigt MSPercury dir explizit, was angelegt wird:

Wird in Stripe angelegt
├─ [Subscription]  6× monatlich wiederkehrend + 2× einmalig auf der ersten Rechnung
│                  — Folgerechnungen rotieren automatisch
└─ [Subscription]  1× jährlich wiederkehrend

Klick auf den Button führt aus:

  1. Stripe-Customer find-or-create (mit Adresse + Tax-ID-Data)
  2. Pro nicht-leerer Rechnungsperiode: subscriptions.create() mit den passenden Items
  3. Einmalige Positionen: werden via add_invoice_items an die erste Rechnung der ersten Subscription gehängt — Kundschaft bekommt eine Mail mit Setup + erstem Monat zusammen
  4. Footer auf jeder Stripe-Rechnung: „MSPercury reference: Q-2026-XXXX” — verlinkt das Angebot per Auge

Was Stripe danach autonom macht:

  • Rotiert die monatlichen / jährlichen Folgerechnungen automatisch (kein zweiter API-Call von uns)
  • Verschickt Hosted-Invoice-Mails aus deinem Branding (Logo + Farbe im Stripe-Dashboard setzen!)
  • Lässt die Kundschaft per Self-Service Zahlungsmethode ändern, kündigen, alte Rechnungen herunterladen
  • Wendet Stripe-Tax-Logik bei jeder Folgerechnung an (Reverse-Charge bleibt aktiv solange die Tax-ID gültig ist)

🟢 „Sofort schicken”-Toggle. Bei reinen Einmal-Angeboten kannst du wählen: Draft im Stripe-Dashboard liegen lassen oder direkt versenden. Bei Subscription-Angeboten immer sofortiger Versand — Stripe finalisiert + emailt die erste Rechnung beim subscriptions.create()-Call. Es gibt kein „Draft-Subscription”-Konzept in der Stripe-API. Falls du das nicht willst: nicht klicken, oder im Stripe-Dashboard die Subscription wieder pausieren / löschen.

🟡 Mengenangabe-Constraint. Stripe akzeptiert für Subscription-Items nur ganzzahlige Mengen (quantity muss int sein). Quote-Lines mit fraktionalen Mengen (z. B. „1.5 Stunden”) werden auf den nächsten Integer gerundet (Math.round(...)). Für Stunden-Pauschalen empfehlen wir: Stunden in Cent oder Minuten umrechnen (z. B. „90 Minuten” als Menge mit „pro Minute”-Einheitspreis), oder eine eigene „Pauschale 1.5 h”-Zeile.

7. Reverse-Charge bei EU-B2B

Stripe Tax wendet die Reverse-Charge-Regel automatisch an, vorausgesetzt:

  • Dein Workspace ist mit Land + USt-IdNr in DE/EU registriert
  • Der Kunde ist in einem anderen EU-Mitgliedstaat (also nicht innerdeutsch)
  • Der Kunde hat einen Tax-ID-Typ eu_vat mit gültiger Nummer (z. B. FR12345678901)

In dem Fall:

  • Stripe-Rechnung: 0 % MwSt, Annotation „Reverse charge — Art. 196 VAT Directive”
  • Quote-PDF aus MSPercury: zeigt zusätzlich den gelben Reverse-Charge-Banner (Art. 196 MwSt-RL / §13b UStG, lokalisiert in der Kundensprache)

💡 Was wenn der Kunde keine USt-IdNr hat? Dann ist es keine B2B-Reverse-Charge-Konstellation, sondern eine B2C-Lieferung — Stripe wendet deinen lokalen Steuersatz an (DE 19 %), du musst die USt im Empfängerland selbst abführen (One-Stop-Shop / OSS-Verfahren). Das ist meistens nicht das, was du mit MSP-Kunden willst. Wenn du den Reverse-Charge-Pfad brauchst, frag aktiv nach der USt-IdNr — und prüf sie über das VIES-Tool bevor du sie einträgst.

⚠️ CH und UK. Schweiz (ch_vat) und UK (gb_vat) sind nicht EU — Reverse-Charge greift nicht. Stripe Tax muss separat für diese Länder konfiguriert werden, falls du dort viele Kunden hast.

8. Häufige Stolpersteine

SymptomUrsacheLösung
Stripe lehnt den Key ab beim SpeichernKey falsch oder Restricted Key hat zu wenige RechtePermissions im Stripe-Dashboard prüfen, neuen Restricted Key mit den oben genannten Scopes erstellen
Erste Rechnung kommt mit 0 % USt obwohl innerdeutschWorkspace hat kein Land / Tax-ID-Typ gesetztEinstellungen → Workspace → Land + Steuer-ID-Typ ergänzen
Reverse-Charge wird nicht angewendetKunde hat kein eu_vat-Type oder leere Tax-IDCustomer-Edit → Land + Type + Tax-ID-Wert komplett füllen, dann erneut pushen
Doppelte Rechnung beim zweiten KlickIdempotenz noch nicht implementiert (Roadmap)Im Stripe-Dashboard die Duplikat-Subscription / -Rechnung manuell stornieren
„Customer hat keine E-Mail-Adresse”E-Mail-Feld am MSPercury-Kunden leerKunde-Edit → E-Mail eintragen — Stripe braucht zwingend eine zum Versand der Hosted-Invoice
Rechnung verschickt aber Kundschaft beklagt fehlende MailStripe-Tax oder Stripe-Account zickt mit Hosted-InvoiceStripe-Dashboard → Invoices → die Rechnung auswählen → „Send again”
Subscriptions-erste-Rechnung landet zwar an, aber Kunde glaubt das ist ALLESStripe-Dashboard → Subscriptions → Footer der Hosted-Invoice individualisierenIn Stripe Tax / Branding den Begleittext anpassen, der auf Folgerechnungen hinweist

9. Was MSPercury nicht macht

Klare Abgrenzung damit du weißt wo Stripe und wo wir die Verantwortung haben:

  • ❌ MSPercury erstellt keine Steuerbescheinigungen, Jahresabschlüsse, USt-Voranmeldungen — das macht weiterhin dein Steuerberater oder dein Buchhaltungs-Tool (DATEV-Schnittstelle steht auf der Roadmap)
  • ❌ MSPercury prüft keine USt-IdNr auf Gültigkeit — du bist verantwortlich für VIES-Validierung bevor du sie in MSPercury einträgst
  • ❌ MSPercury stornieren keine Stripe-Rechnungen — wenn ein Kunde abspringt, im Stripe-Dashboard die Subscription pausieren / kündigen + die offene Rechnung void-en
  • ❌ MSPercury archiviert keine Stripe-Rechnungen — die liegen in deinem Stripe-Konto, du bist für GoBD-konforme Aufbewahrung selbst zuständig (Stripe hat Export-Features)
  • ❌ MSPercury bearbeitet keine Refund-Anfragen, Chargebacks, Disputes — alles im Stripe-Dashboard
  • ❌ MSPercury kennt nicht dein Stripe-Saldo, deine Auszahlungen, deine Steuerlast — wir sehen nur Rechnungs-IDs, keine Geldflüsse

Was MSPercury macht:

  • ✅ Aus jedem akzeptierten Angebot eine Stripe-Rechnung (oder Subscription) auf deinem Konto erstellen
  • ✅ Customer-Stammdaten + Tax-IDs in Stripe synchronisieren bei jedem Push
  • ✅ Footer / Metadata auf jeder Stripe-Rechnung mit Angebotsreferenz versehen — du kannst im Stripe-Dashboard zurück zum Angebot springen
  • ✅ Den verschlüsselten API-Key sicher in deinem Workspace speichern, nie an Dritte rausgeben

10. Roadmap

Was noch kommt:

  • Idempotenz: zweiter Klick auf „Stripe-Rechnung erstellen” wird erkannt, kein Duplikat
  • Stripe-Subscription-IDs auf der Quote-Row: damit /quotes/[id] zeigen kann „bereits an Stripe geschickt — ID acct_…”, inkl. Direkt-Link ins Stripe-Dashboard
  • Webhook-Sync: wenn die Stripe-Subscription gekündigt wird, MRR im Dashboard automatisch aktualisieren
  • Lexware Office, sevDesk, Polar.sh: alternative Connectors für MSPs die nicht Stripe nutzen — siehe Roadmap

Wenn dir was Konkretes fehlt: Discord-Community, Channel #stripe-billing. Lucas liest mit.