Öffentliche Bezahlseite
Zweck
Die öffentliche Bezahlseite gibt Ihren Kunden eine Web-Adresse, unter der sie eine bestimmte Rechnung ohne Login bezahlen können — per Banküberweisung (mit GiroCode-QR fürs Banking-App-Scannen) oder per PayPal. Nach einer erfolgreichen PayPal-Zahlung gleicht SpeamCore den zugehörigen offenen Posten automatisch aus; die genaue Mechanik beschreibt das Konzept PayPal-Zahlungsannahme.
Die Seite läuft unter der Route /pay/:ref und wird ohne den üblichen App-Rahmen angezeigt (kein Menü, keine Anmeldung).
Voraussetzungen
Berechtigungen (CASL)
Die Seite ist öffentlich — es gibt keine CASL-Prüfung und keine Anmeldung. An ihre Stelle tritt eine fachliche Zugangskontrolle:
- Der
refin der URL ist die zufällige, nicht erratbare UUID des Bezahllinks (InvoicePaymentLink.id). - Zusätzlich muss der Kunde Rechnungsnummer und Kundennummer angeben (beide aus dem jeweiligen Nummernkreis). Stimmt eines nicht, antwortet der Server mit einem generischen Fehler.
- Ist die Bezahlseite in den Einstellungen ausgeschaltet, ist jeder Link „nicht gefunden".
Beträge und der Rechnungsbezug werden immer serverseitig aus dem Link abgeleitet — nie aus der Anfrage des Browsers übernommen.
So gelangt der Kunde auf die Seite
- Auf der Rechnung (PDF/Druck) steht ein QR-Code. Welcher QR gedruckt wird, steuert der QR-Modus in den Rechnungsausgang-Einstellungen:
- „Jetzt bezahlen"-QR (
payLinkQr) → führt direkt auf/pay/:ref; Rechnungs- und Kundennummer werden aus dem Link als Parameter mitgegeben. - Bank-QR (
bankQr) → klassischer GiroCode für die Banking-App (keine Bezahlseite).
- „Jetzt bezahlen"-QR (
- Alternativ können Sie den Link manuell aus dem Bezahllink der Rechnung weitergeben.
- Öffnet der Kunde den Link ohne mitgelieferte Parameter, fragt die Seite Rechnungs- und Kundennummer ab.
Schritt-für-Schritt — Kundensicht
- Kunde öffnet
/pay/:ref(per QR oder Link). - Falls nötig: Rechnungsnummer und Kundennummer eingeben → die Seite lädt die Rechnungsdaten (
POST /api/public-pay/:ref/info). - Die Seite zeigt Absender (Firma), Rechnungsnummer, Rechnungsdatum, Fälligkeit und den zu zahlenden Betrag. Bei Überfälligkeit erscheint ein entsprechender Hinweis; liegt ein gültiges Skonto-Fenster vor, wird der rabattierte Betrag angeboten.
- Kunde wählt einen Zahlungsweg:
- Banküberweisung → GiroCode-QR zum Scannen in der Banking-App (IBAN, BIC, Betrag, Verwendungszweck „Rechnung …" sind hinterlegt).
- PayPal → der PayPal-Button (über
POST /api/public-pay/:ref/orderwird die Order angelegt, nach Bestätigung folgt…/capture).
- Nach erfolgreicher PayPal-Zahlung zeigt die Seite die Bestätigung („Zahlung am … via PayPal").
<Screenshot status="todo" beschreibung="Öffentliche Bezahlseite: Firmenkopf, Rechnungsnummer und Betrag, Fälligkeits-Banner, darunter zwei Kacheln „Banküberweisung (GiroCode-QR)" und „PayPal" mit dem PayPal-Button." />
Was die Seite anzeigt
| Anzeige | Bedeutung | Voraussetzung |
|---|---|---|
| Firmenname | Absender der Rechnung (aus companyName) | — |
| Rechnungsnummer / -datum | identifiziert den Beleg | korrekte Eingabe der Nummern |
| Fälligkeit + Status | overdue (überfällig), dueToday (heute fällig) oder planned | Rechnung noch offen |
| Zu zahlender Betrag | offener Betrag des Postens (ggf. Skonto-rabattiert) | — |
| Skonto-Hinweis | rabattierter Betrag inkl. Frist | Skonto-Fenster aktuell offen |
| Geschätzte PayPal-Gebühr | Aufschlag, falls Gebühren-Weitergabe aktiv | paypalSurchargeEnabled + Gebührenkonto |
| Zahlungswege | Banküberweisung und/oder PayPal | in den Einstellungen freigegeben |
| Bezahlt-Ansicht | „Zahlung am … via PayPal/Bank" | offener Posten ausgeglichen |
Nach der Zahlung — automatischer Ausgleich
Nach erfolgreicher PayPal-Capture bucht SpeamCore im Hintergrund eine Transaktion und eine Zuordnung gegen die Rechnung — der offene Posten ist damit ausgeglichen, und die Capture-ID wird als paypalTransactionCode auf der Rechnung hinterlegt. Trifft zusätzlich der PayPal-Webhook ein, wird nicht doppelt gebucht (Idempotenz über die externalId). Details: PayPal-Zahlungsannahme.
Eine Banküberweisung wird wie gewohnt über den Kontoumsatz und die Auto-Allocation zugeordnet — die Bezahlseite stellt hier nur den GiroCode bereit, löst aber selbst keine Buchung aus.
API/Schnittstellen
Alle Endpunkte sind öffentlich (kein Keycloak/CASL); die Autorisierung erfolgt über ref + Rechnungs-/Kundennummer.
| Methode | Endpoint | Zweck |
|---|---|---|
POST | /api/public-pay/:ref/info | Rechnungsdaten und freigegebene Zahlwege laden |
POST | /api/public-pay/:ref/order | PayPal-Order über den offenen Betrag anlegen |
POST | /api/public-pay/:ref/capture | bestätigte Order abbuchen (Capture) → Settlement |
POST | /api/public-pay/:ref/sdk-token | Client-Token für das PayPal-JS-SDK |
Verknüpfungen zu anderen Modulen
- Rechnungsausgang — Einstellungen — Bezahlseite ein/aus, Methoden, QR-Modus, Gebühren-Weitergabe.
- Zahlungskonten — PayPal-Anbindung (Zugangsdaten, Webhook).
- Verkaufsbelege — Bezahlung per PayPal-Link und
paypalTransactionCode. - Offene Posten — werden nach PayPal-Zahlung automatisch ausgeglichen.
Wiederverwendbare Konzepte
Häufige Fehler und Lösungen
| Fehler | Lösung |
|---|---|
| „Nicht gefunden" trotz gültigem Link | Bezahlseite ist in den Einstellungen deaktiviert oder der Bezahllink ist inactive |
| „Autorisierung fehlgeschlagen" | Rechnungs- oder Kundennummer stimmt nicht (Format ignoriert Leerzeichen/Groß-/Kleinschreibung) |
| Kein PayPal-Button | PayPal nicht als Methode freigegeben oder keine aktive Anbindung mit paymentsEnabled |
| Kein GiroCode | Banküberweisung nicht freigegeben oder keine Bankverbindung an der Niederlassung |
| Betrag wirkt zu niedrig | gültiges Skonto-Fenster — es wird der rabattierte Betrag eingezogen |
Versionshinweise
- 2026-06-22: Initiale Veröffentlichung — öffentliche Bezahlseite
/pay/:refmit Banküberweisung (GiroCode) und PayPal. Verifiziert anPublicPaymentPage.tsx,publicPayment.router.ts,publicPayment.service.tsundpaypalOrders.service.ts.