Skip to content

Abrechnung mit Stripe ​

PollYou verkauft Guthaben für Umfragen (Modell und Preise: Preise und Guthaben) über Stripe: Stripe Checkout für die einmalige Zahlung mit Rechnung, Stripe Tax für die Umsatzsteuer, das Stripe Customer Portal für Rechnungen und Rechnungsdaten. Es gibt keine Abonnements, keine Testphase, keine Planwechsel und kein Mahnwesen. Inhaberinnen und Inhaber einer Organisation finden alles unter Einstellungen → Guthaben.

Ist auf dem Server kein Stripe-Schlüssel hinterlegt, zeigt die Seite „Kauf noch nicht verfügbar“; Guthaben vergibt dann der Support (bin/console app:credits:grant, siehe Für den Support).

Preise ​

PaketPreisLookup-Key in Stripe
5 Umfragen4,95 €pollyou_credits_5
10 Umfragen9,90 €pollyou_credits_10
25 Umfragen24,75 €pollyou_credits_25
50 Umfragen49,50 €pollyou_credits_50

0,99 € je Umfrage, ohne Mengenrabatt. Die Preise sind Bruttopreise (inkl. MwSt.) und stehen an genau einer Stelle, App\Billing\Credit\CreditPacks (Cent, Euro); Stripe-Preise (app:billing:sync-products), Dashboard und Landing Page (make plans-export) lesen sie von dort.

Ablauf für Kundinnen und Kunden ​

Guthaben kaufen ​

  1. Einstellungen → Guthaben: Paket wählen und kaufen.
  2. Stripe Checkout öffnet sich: Rechnungsadresse (Pflicht), optional die USt-IdNr. des Unternehmens, Zahlungsmittel, ggf. ein Gutscheincode.
  3. Nach dem Abschluss kehrt der Browser zur Guthaben-Seite zurück. Sobald Stripe die Zahlung meldet (Webhook, meist nach wenigen Sekunden), ist das Guthaben gebucht.

Zahlungsmittel mit verzögerter Bestätigung (z. B. SEPA-Lastschrift) buchen das Guthaben erst, wenn Stripe die Zahlung bestätigt (checkout.session.async_payment_succeeded, kann einige Tage dauern). Jeder Kauf erzeugt eine Rechnung (invoice_creation).

Rechnungen und Rechnungsdaten ​

Die letzten 24 Rechnungen zeigt die Guthaben-Seite (mit Link zur Rechnung und zum PDF); sie werden bei jedem Aufruf von Stripe gelesen, PollYou speichert keine Rechnungen. „Rechnungen und Rechnungsdaten“ öffnet das Stripe Customer Portal: Rechnungshistorie, Rechnungsadresse, E-Mail, USt-IdNr. und gespeicherte Zahlungsmittel. Das Portal gibt es erst nach dem ersten Kauf (vorher existiert kein Stripe-Kunde).

Die Buchungsliste der Guthaben-Seite zeigt jede Gutschrift und jeden Verbrauch (Kauf, Willkommensguthaben, Geschenk, Erstattung, Veröffentlichen, Verlängern).

Erstattungen ​

Erstattungen sind nicht automatisiert. Der Support erstattet die Zahlung im Stripe-Dashboard (Payments → Refund) und nimmt die Guthaben zurück:

sh
bin/console app:credits:grant <organisation> -5 --reason=refund --note="Erstattung Rechnung …"

Der Kontostand darf dabei unter 0 fallen, wenn Guthaben schon verbraucht ist; bereits bezahlte Umfragen laufen weiter.

Steuern (EU-Umsatzsteuer) ​

  • Die Preise sind Bruttopreise (tax_behavior: inclusive): Der angezeigte Betrag enthält die Umsatzsteuer, Stripe Tax rechnet sie heraus. Das Produkt trägt den Steuercode „Software as a Service – geschäftliche Nutzung“ (txcd_10103001).
  • Stripe Tax berechnet die Steuer im Checkout (automatic_tax, abschaltbar mit BILLING_AUTOMATIC_TAX=0) anhand der Rechnungsadresse (im Checkout Pflicht).
  • Deutschland: 19 % Umsatzsteuer, auch für Unternehmen – 0,99 € enthalten 0,16 € USt.
  • Privatpersonen in der EU zahlen die Umsatzsteuer ihres Landes (B2C-Regel für elektronische Dienstleistungen); der Bruttopreis bleibt gleich, nur der Steueranteil ändert sich. Gemeldet wird über das OSS-Verfahren – dafür muss im Stripe-Dashboard die OSS-Registrierung eingetragen sein.
  • Unternehmen in der EU (außerhalb Deutschlands) geben im Checkout oder im Portal ihre USt-IdNr. an: Stripe validiert sie (VIES) und stellt im Reverse-Charge-Verfahren ohne Umsatzsteuer aus; die Rechnung trägt den Reverse-Charge-Hinweis. Bei Bruttopreisen bleibt der Betrag gleich (0,99 € je Umfrage), er enthält dann nur keine Steuer.
  • Außerhalb der EU: in der Regel keine Umsatzsteuer; Stripe Tax berechnet, wo eine Registrierung vorliegt.

Voraussetzung im Stripe-Dashboard: Settings → Tax aktiviert, Firmensitz und Registrierungen (Deutschland, OSS) eingetragen. Wie Stripe Bruttopreise bei Reverse Charge und ausländischen Steuersätzen behandelt, vor der Live-Schaltung im Testmodus prüfen (siehe Runbook). Die steuerliche Bewertung (Kleinunternehmer, Registrierungen) gehört zur Steuerberatung, nicht zu PollYou.

Einrichtung (Betrieb) ​

Umgebungsvariablen ​

VariableBedeutung
STRIPE_SECRET_KEYSecret Key (sk_test_… / sk_live_… oder Restricted Key). Leer = Kauf aus. API, Worker, Scheduler
STRIPE_WEBHOOK_SECRETSigning Secret des Webhook-Endpunkts (whsec_…). Leer = Webhooks abgewiesen (404). Nur die API
STRIPE_API_BASENur für stripe-mock (Entwicklung); in Produktion leer
BILLING_AUTOMATIC_TAXStripe Tax im Checkout (Standard 1; braucht aktiviertes Stripe Tax)

Alle sind in backend/.env.prod leer bzw. mit Standardwerten vorbelegt; das Produktions-Image startet ohne sie (Kauf aus). Die früheren Variablen STRIPE_PRICE_<PLAN>_<INTERVAL>, BILLING_TRIAL_DAYS und BILLING_GRACE_DAYS gibt es nicht mehr; sie können aus der Konfiguration entfernt werden.

Produkte und Preise anlegen ​

sh
bin/console app:billing:sync-products            # im Worker-Container
bin/console app:billing:sync-products --dry-run  # nur anzeigen

Legt aus CreditPacks das Produkt pollyou_survey_credits („PollYou Umfrage-Guthaben“) und je Paket einen einmaligen Bruttopreis mit dem Lookup-Key pollyou_credits_<n> an, außerdem die Konfiguration des Customer Portals (Rechnungen, Rechnungsadresse, E-Mail, USt-IdNr., Zahlungsmittel; Rückweg auf die Guthaben-Seite). Der Befehl ist idempotent: ein zweiter Lauf meldet nur „unchanged“. Ändert sich ein Betrag, entsteht ein neuer Preis, der den Lookup-Key übernimmt; der alte wird archiviert. Einmal je Stripe-Konto und Modus (Test/Live) und nach jeder Preisänderung ausführen. Produkte und Preise der früheren Abonnements (pollyou_<plan>) bleiben in Stripe liegen und lassen sich dort archivieren.

Webhook-Endpunkt ​

Im Stripe-Dashboard (Developers → Webhooks → Add endpoint):

  • URL: https://api.<domain>/api/billing/stripe/webhook
  • Ereignisse: checkout.session.completed, checkout.session.async_payment_succeeded
  • Das Signing Secret als STRIPE_WEBHOOK_SECRET in die API-App eintragen.

Ein bestehender Endpunkt aus der Abo-Zeit braucht nur diese beiden Ereignisse; die customer.subscription.*- und invoice.*-Ereignisse abwählen (sie würden nur ignoriert).

Der Endpunkt prüft die Signatur (höchstens 5 Minuten alt) und verarbeitet jedes Ereignis genau einmal (Ereignis-IDs in billing_webhook_event, 90 Tage aufbewahrt). Gebucht wird nur eine bezahlte Checkout-Session (mode: payment) eines bekannten Pakets, und jede Session höchstens einmal – auch wenn completed und async_payment_succeeded beide eintreffen. Fehler beim Anwenden beantwortet er mit 500, Stripe wiederholt das Ereignis dann bis zu drei Tage lang.

Endpunkte ​

EndpunktWerWas
GET /api/organizations/{id}/billingInhaberKauf möglich?, Stripe-Kunde vorhanden?, Kontostand, Preis, Pakete
POST /api/organizations/{id}/billing/checkoutInhaber{"pack": 10} → {url} der Checkout-Seite
POST /api/organizations/{id}/billing/portalInhaber{url} des Customer Portals (409 vor dem ersten Kauf)
GET /api/organizations/{id}/billing/invoicesInhaberdie letzten 24 Rechnungen (von Stripe gelesen)
GET /api/organizations/{id}/billing/creditsInhaberBuchungen, neueste zuerst, 25 je Seite (?page=)
POST /api/surveys/{id}/extendRolle mit Veröffentlichen-RechtUmfrage um ein Jahr verlängern (402 no_credits ohne Guthaben)
POST /api/billing/stripe/webhookStripeWebhook (Signatur Stripe-Signature)

Stripe-Fehler beantworten die Dashboard-Endpunkte mit 503 (ohne Stripes Meldung).

Kontrolle ​

sh
bin/console app:billing:show <organisation-id|slug>   # Plan, Guthaben, Stripe-Kunde, letzte Buchungen

Jede Buchung steht im Audit-Log der Organisation (credits.added, credits.used). Den Ablauf im echten Stripe-Testmodus beschreibt das Runbook.

Entwicklung ​

Der Entwicklungs-Stack (docker-compose.yml) enthält stripe-mock, eine zustandslose Simulation der Stripe-API; die API zeigt mit STRIPE_API_BASE darauf. Damit funktionieren Checkout- und Portal-Aufrufe, es gibt aber keine Checkout-Seite und keine Webhooks. Ereignisse lassen sich mit dem Signing Secret whsec_pollyou_dev selbst schicken (siehe e2e/support/billing.ts). Neue Organisationen haben im Stack den Plan internal und brauchen kein Guthaben; zum Ausprobieren erst auf Standard setzen (docker compose exec php bin/console app:organization:set-plan <slug> standard).

Für die echte Testumgebung: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET setzen und STRIPE_API_BASE= leer übergeben (siehe Runbook).