Files
aza/AzA march 2026/deploy/aza-deploy/STRIPE_LIVE_SETUP.md
2026-03-30 07:59:11 +02:00

8.7 KiB
Raw Blame History

AZA Stripe Live-Setup — Vollständige Anleitung

Stand: 14. März 2026


1. Ist-Analyse: Was existiert im Code

Zwei Stripe-Integrationen (parallel)

Datei Env-Prefix Methode Status
stripe_routes.py STRIPE_* (kein Prefix) Lookup-Keys (aza_basic_monthly etc.) Primär / Aktiv
license_server.py AZA_STRIPE_* Direkte Price-IDs Legacy / Parallel

Wichtig: stripe_routes.py ist der aktive Pfad. Die Landing-Page (web/index.html) ruft /stripe/create_checkout_session auf und übergibt einen lookup_key.

Erwartete Lookup-Keys im Code

Lookup-Key Plan Policy
aza_basic_monthly Basic (1 User, 2 Geräte) Checkout auf Landing-Page
aza_basic_yearly Basic (1 User, 2 Geräte) Noch nicht auf Landing-Page
aza_team_monthly Team (3 User, 2 Geräte/User) Checkout auf Landing-Page
aza_team_yearly Team (3 User, 2 Geräte/User) Noch nicht auf Landing-Page

Aktuelle Preise auf der Landing-Page (web/index.html)

Plan Preis (monatlich) Preis (jährlich) Lookup-Key (monatlich) Lookup-Key (jährlich)
AZA Basic (1 User) CHF 59 / Monat CHF 590 / Jahr aza_basic_monthly aza_basic_yearly
AZA Team (2 User) CHF 89 / Monat CHF 890 / Jahr aza_team_monthly aza_team_yearly

Fehlend auf der Landing-Page

  • Jahreslizenz (17 % günstiger) ist NICHT dargestellt
  • Nur monatliche Preise sind sichtbar
  • Kein Umschalter Monatlich/Jährlich

2. Env-Variablen-Diskrepanz (KRITISCH)

stripe_routes.py liest:

STRIPE_SECRET_KEY          ← deploy/.env setzt diese ✓
STRIPE_WEBHOOK_SECRET      ← deploy/.env setzt diese ✓
STRIPE_SUCCESS_URL         ← deploy/.env setzt diese ✓
STRIPE_CANCEL_URL          ← deploy/.env setzt diese ✓
STRIPE_PORTAL_RETURN_URL   ← deploy/.env setzt diese ✓

license_server.py liest:

AZA_STRIPE_SECRET_KEY       ← deploy/.env setzt diese NICHT ✗
AZA_STRIPE_WEBHOOK_SECRET   ← deploy/.env setzt diese NICHT ✗
AZA_STRIPE_PRICE_BASIC      ← deploy/.env setzt diese NICHT ✗
AZA_STRIPE_PRICE_TEAM       ← deploy/.env setzt diese NICHT ✗
AZA_STRIPE_PRICE_BASIC_YEARLY ← deploy/.env setzt diese NICHT ✗
AZA_STRIPE_PRICE_TEAM_YEARLY  ← deploy/.env setzt diese NICHT ✗
AZA_STRIPE_SUCCESS_URL      ← deploy/.env setzt diese NICHT ✗
AZA_STRIPE_CANCEL_URL       ← deploy/.env setzt diese NICHT ✗

Zusätzlich in license_server.py (Zeile 629, 867):

webhook_secret = os.getenv("STRIPE_WEBHOOK_SECRET")     # ohne AZA_
secret_key = os.getenv("STRIPE_SECRET_KEY") or os.getenv("STRIPE_API_KEY")  # ohne AZA_

→ Inkonsistenz innerhalb derselben Datei.

Konsequenz

  • stripe_routes.py funktioniert mit deploy/.envOK
  • license_server.py Checkout-Endpoint bekommt leere Price-IDs → Broken
  • Aber: Landing-Page nutzt /stripe/create_checkout_session (= stripe_routes.py) → OK
  • Der license_server.py Checkout ist ein Parallel-Pfad, der aktuell nicht aufgerufen wird

Empfehlung

Für Go-Live: stripe_routes.py ist der korrekte Pfad. Kein Umbau nötig. Die license_server.py Env-Vars mit AZA_* Prefix können später bereinigt werden.


3. Was im Stripe-Dashboard angelegt werden muss

Produkte und Preise

Gehe zu: Stripe Dashboard → Produkte → + Produkt hinzufügen

Produkt 1: AZA Desktop Praxis

Feld Wert
Name AZA Desktop Praxis
Beschreibung Medizinische KI-Software für Einzelpraxen. Alle 6 Module.

Preise für dieses Produkt:

Preis Intervall Lookup-Key (WICHTIG!) Price-ID
CHF 59.00 Monatlich aza_basic_monthly price_1T53xHL5lREAW68VbuK43lmz
CHF 590.00 Jährlich aza_basic_yearly price_1T542BL5lREAW68VNLQGCKWZ

Produkt 2: AZA Desktop Team (2 Benutzer)

Feld Wert
Name AZA Desktop Team
Beschreibung Für Gemeinschaftspraxen. Bis zu 2 Benutzer.

Preise für dieses Produkt:

Preis Intervall Lookup-Key (WICHTIG!) Price-ID
CHF 89.00 Monatlich aza_team_monthly price_1T544tL5lREAW68VkmnmZ21Q
CHF 890.00 Jährlich aza_team_yearly price_1T545RL5lREAW68VLbIh73AN

KRITISCH: Beim Erstellen jedes Preises den Lookup-Key setzen! In Stripe: Preis erstellen → Erweiterte Optionen → Lookup Key → exakt den Key eingeben.


4. Webhook-Setup

Webhook-Endpunkt

Feld Wert
URL https://app.aza-medwork.ch/stripe/webhook
Oder (falls auf Hostpoint) https://aza-medwork.ch/stripe/webhook

Hinweis: Der Webhook-Endpunkt muss auf dem Server laufen, auf dem stripe_routes.py läuft. Aktuell ist das der Hetzner-Server (Docker/Caddy). Erst relevant, wenn der Backend-Server auf Hetzner live ist.

Für den Start über WooCommerce/Hostpoint: WooCommerce Stripe Plugin hat seinen eigenen Webhook — der wird automatisch konfiguriert.

Benötigte Webhook-Events

Event Zweck
checkout.session.completed Neue Subscription registrieren, Lizenz anlegen
customer.subscription.updated Statusänderungen (Verlängerung, Pause, Planwechsel)
customer.subscription.deleted Kündigung, Lizenz deaktivieren

Webhook-Secret

Nach dem Erstellen des Webhooks zeigt Stripe ein whsec_... Secret an. Dieses muss in deploy/.env als STRIPE_WEBHOOK_SECRET eingetragen werden.


5. Success/Cancel-Flow — Prüfung

Success-Flow ✓

  • URL: https://aza-medwork.ch/billing/success?session_id={CHECKOUT_SESSION_ID}
  • Endpunkt: backend_main.py@app.get("/billing/success")
  • Liest session_id aus Query-Parameter
  • Ruft stripe.checkout.Session.retrieve(session_id) auf
  • Zeigt Bestätigungsseite mit Download-Link und Anleitung
  • Funktioniert korrekt (lokal verifiziert)

Cancel-Flow ✓

  • URL: https://aza-medwork.ch/billing/cancel
  • Endpunkt: backend_main.py@app.get("/billing/cancel")
  • Zeigt Info-Seite "Checkout abgebrochen" mit Link zurück
  • Funktioniert korrekt (lokal verifiziert)

Portal-Return ✓

  • URL: https://aza-medwork.ch/
  • Für Stripe Billing Portal (Abo verwalten, kündigen)

6. Benötigte Live-Secrets für deploy/.env

# 1. Stripe Secret Key (Dashboard → Developers → API keys → Secret key)
STRIPE_SECRET_KEY=sk_live_XXXXXXXXXXXXXXXXXXXXXX

# 2. Stripe Webhook Secret (Dashboard → Developers → Webhooks → Signing secret)
STRIPE_WEBHOOK_SECRET=whsec_XXXXXXXXXXXXXXXXXXXXXX

# 3. API-Token für Backend-Zugriff (selbst generieren, z.B. openssl rand -hex 32)
MEDWORK_API_TOKENS=<TOKEN_1>,<TOKEN_2>

Die übrigen Werte in deploy/.env sind bereits korrekt gesetzt:

  • STRIPE_SUCCESS_URL
  • STRIPE_CANCEL_URL
  • STRIPE_PORTAL_RETURN_URL
  • AZA_DOMAIN
  • ACME_EMAIL

7. Offene Punkte / Landing-Page-Update nötig

web/index.html muss aktualisiert werden:

  1. Basic (1 User): CHF 59/Monat → aza_basic_monthly, CHF 590/Jahr → aza_basic_yearly
  2. Team (2 User): CHF 89/Monat → aza_team_monthly, CHF 890/Jahr → aza_team_yearly
  3. Alle vier Preise sind in Stripe angelegt mit korrekten Lookup-Keys

8. Zwei Wege zum Verkauf (Entscheidung nötig)

Weg A: WooCommerce Subscriptions (Hostpoint)

  • Alles auf der bestehenden WordPress-Seite
  • WooCommerce Subscriptions Plugin nötig (kostenpflichtig, ca. $200/Jahr)
  • WooCommerce Stripe Gateway macht Webhooks automatisch
  • Kein eigener Backend-Server nötig für den Verkauf
  • Download-Auslieferung über WooCommerce
  • Vorteil: Kein Hetzner-Deploy nötig für den Start

Weg B: Eigener Stripe-Flow (Hetzner)

  • Landing-Page + stripe_routes.py + Backend auf Hetzner
  • Voller Kontrolle über den Checkout-Flow
  • Webhook an eigenen Server
  • Nachteil: Hetzner muss erst deployed werden

Empfehlung für sofortigen Start

Weg A (WooCommerce) für den Verkauf. Weg B später für Browser-AZA.


Zusammenfassung

Vorhanden ✓

  • Stripe-Integration im Code komplett (stripe_routes.py)
  • Checkout-Session-Erstellung mit Lookup-Keys
  • Webhook-Handler für 3 Events (checkout.completed, subscription.updated/deleted)
  • Idempotente Event-Verarbeitung (Deduplizierung)
  • Lizenz-Datenbank (SQLite) mit Upsert-Logik
  • Success/Cancel-Seiten implementiert
  • Billing-Portal-Integration
  • Env-Variablen-Template (deploy/.env.example)

Fehlt ✗

  • Live Stripe-Produkte + Preise im Stripe-Dashboard (mit korrekten Lookup-Keys)
  • Live Stripe Secret Key in deploy/.env
  • Live Webhook Secret in deploy/.env
  • Jahreslizenz auf Landing-Page (nur monatlich dargestellt)
  • Entscheidung: WooCommerce vs. eigener Flow für den sofortigen Start
  • Hetzner-Deploy (nur nötig für Weg B)