Files
aza/AzA march 2026 - Kopie (14)/deploy/STRIPE_LIVE_SETUP.md
2026-04-19 20:41:37 +02:00

251 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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):
```python
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/.env`**OK**
- `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 (LIVE) | `https://api.aza-medwork.ch/stripe/webhook` |
| Hinweis | Webhook laeuft auf Hetzner (api.aza-medwork.ch), NICHT auf Hostpoint |
**Hinweis:** Der Webhook-Endpunkt laeuft auf dem Hetzner-Server (Docker/Caddy) unter
`https://api.aza-medwork.ch/stripe/webhook`. Dieser Pfad ist LIVE und funktional (2026-03-28).
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
```bash
# 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`)
### Erledigt seit 2026-03-28 ✓
- ~~Live Stripe-Produkte + Preise~~ → Produkt AzA Office mit 4 Preisen angelegt (aza_basic_monthly, aza_basic_yearly, aza_team_monthly, aza_team_yearly)
- ~~Live Stripe Secret Key~~ → STRIPE_SECRET_KEY in deploy/.env gesetzt
- ~~Live Webhook Secret~~ → STRIPE_WEBHOOK_SECRET in deploy/.env gesetzt
- ~~Hetzner-Deploy~~ → api.aza-medwork.ch LIVE, Webhook functional (200 OK)
- Sandbox-Kauf erfolgreich verarbeitet (admin@aza-medwork.ch, aza_basic_monthly, active)
- lookup_key-Fallback aktiv via _lookup_key_from_price() fuer Sandbox ohne price.lookup_key
### Noch offen ✗
- **Jahreslizenz auf Landing-Page** (nur monatlich dargestellt)
- **Desktop-Lizenzpfad** gegen /stripe/license_debug validieren
- **WooCommerce-Grundkonfiguration** (getrennter Admin-Block)