Workers P5: Betriebsanleitung nach der Endprüfung (Reihenfolge, Stripe-Ereignis, Testbestellung, Rechtsrunde, Kauf-Link-Schalter, Rückweg)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
999731d882
commit
4e743f11ca
1 file changed
+396
-27
@@ -1088,33 +1088,402 @@ git commit -m "feat(workers-kauf): Testangebot-Skript für die manuelle Prüfung
|
|||||||
|
|
||||||
## Betriebsanleitung (Kenso)
|
## Betriebsanleitung (Kenso)
|
||||||
|
|
||||||
Vorher: P1 ist live (Container `webklar-workers` läuft, Sammlungen angelegt), und der Branch `feature/workers-kaufweg` ist geprüft. Agenten pushen nicht und starten keine Container; die Schritte führst du aus. Immer `up -d --no-deps <dienst>`, nie ein nacktes `up -d`, nie `preview-portal` neu anlegen.
|
Reihenfolge einhalten: Block A bis J. **[Claude]** macht Claude für dich, alles andere tippst du selbst auf dem Server (jeder Befehl in seinem Kasten; `$T`, `$T2` und `$T3` aus E3, E12 und E10a gelten nur in derselben Shell). Zugangsdaten tippst du nur selbst ein (`read -rs` oder direkt in `.env`), nie in einen Chat und nie in einen Befehl, der sie ausgibt; die `.env` zeigst du nie an, die Befehle unten zählen nur Zeilen. Nach jeder Änderung an `.env` oder `docker-compose.yml` startest du den betroffenen Dienst mit `cd /home/webklar/apps && docker compose up -d --no-deps <dienst>` neu (nie `docker restart`: das liest `.env` nicht neu ein; nie ein nacktes `docker compose up -d`; nie `preview-portal` neu anlegen). Wird ein `git push` abgelehnt: nie `--force`, Claude Bescheid. Agenten führen von diesen Befehlen nichts aus. Der Start des Portal-Containers installiert Pakete und dauert 1 bis 2 Minuten (das gilt auch nach jedem Deploy): in der Zeit ist das Portal nicht erreichbar, lege Neustarts also in ruhige Zeiten.
|
||||||
|
|
||||||
1. **Sammlungen prüfen.** Die fünf Sammlungen müssen existieren (Befehl und Dienst-Pfad laut P1-Betriebsanleitung):
|
Was P5 tut: Der Hustler schickt einem Betrieb einen Link auf `https://project.webklar.com/uebernahme.html#t=<Token>`. Dort bestellt der Betrieb ohne Anmeldung das Hosting-Abo (Kundenart, Anschrift, Startwahl) und zahlt bei Stripe. Im Webhook entstehen dann sein Kundenkonto, der Zustimmungsnachweis und sein Projekt aus der Vorschau (Repo mit dem einen Commit „Erste Version der Website“); er bekommt K01 (Vertragsbestätigung) und K44 (Zugangsdaten), und im Hustler wird der Interessent zum Deal. **Achtung:** Der Kaufweg kennt keinen Pilotkreis (`ABO_PILOT_EMAILS` gilt hier nicht): Sobald `WORKERS_KAUF_ENABLED=on` steht und der Hustler einen Link verschickt hat, kauft jeder mit gültigem Link. Darum diese Reihenfolge: Schalter aus deployen (C), Stripe vorbereiten (D), Testbestellung (E), Rechtsfreigabe (F), erst dann einschalten (G). Die Testbestellungen laufen gegen Stripe live: Lauf 2 bucht 0,50 € ab, Lauf 1 erst nach der Widerrufsfrist, und bis dahin kündigst du ihn (E13).
|
||||||
`docker exec webklar-workers timeout 120 node --max-old-space-size=256 scripts/setup-collections.mjs --check`
|
|
||||||
Prüfung: Exit-Code 0, Ausgabe nennt `workersOffers`, `workersProspects`, `workersPreviews`, `workersEvents`, `workersSettings` als vorhanden.
|
### A. Voraussetzungen prüfen
|
||||||
2. **Backups.** `cd /home/webklar/apps && cp .env .env.bak-$(date +%F)-workers && cp docker-compose.yml docker-compose.yml.bak-$(date +%F)-workers`. Prüfung: beide `*.bak-…-workers` per `ls -l` vorhanden.
|
|
||||||
3. **`.env`.** Zeile `WORKERS_KAUF_ENABLED=off` ergänzen (Schalter zunächst aus). Prüfung: `rg -n '^WORKERS_KAUF_ENABLED' .env` zeigt genau eine Zeile.
|
**A1. P1, P3 und P4 laufen.**
|
||||||
4. **Compose, Dienst `kundenbereich-server`.** Unter `environment:` zwei Zeilen, unter `volumes:` eine Zeile ergänzen:
|
```
|
||||||
```yaml
|
curl -s https://ticket.webklar.com/api/workers/health
|
||||||
# Hustler-Kaufweg (2026-10-17): Seite "Website uebernehmen" zuerst aus; Vorschau-Dateien lesend aus dem Volume workers_data
|
```
|
||||||
|
*Erwartet:* `{"ok":true,"version":"0.1.0"}`. *Wenn nicht:* erst die Betriebsanleitungen von P1, P3 und P4 abarbeiten (bis zu ihrem „Fertig, wenn“).
|
||||||
|
|
||||||
|
**A2. Der Dienst hat den Stand mit dem Angebotssatz je Angebot** (Dienst auf `558ce24` oder neuer; im Container, nicht nur im Checkout).
|
||||||
|
```
|
||||||
|
docker exec webklar-workers grep -c offerText /app/src/gespraeche/theo.js
|
||||||
|
```
|
||||||
|
*Erwartet:* eine Zahl über 0 (heute 13). *Wenn nicht:* P4 B7 und B8 (Push, Pull, Neubau) mit dem aktuellen Stand von `p4-gespraeche` nachholen; Claude nennt dir die endgültige Nummer. Dazu muss der Kauf-Link noch aus sein:
|
||||||
|
```
|
||||||
|
docker exec webklar-workers printenv WORKERS_KAUFLINK
|
||||||
|
```
|
||||||
|
*Erwartet:* `nein`. *Wenn nicht:* `ja` heißt, ein Kauf-Link zeigt auf eine Seite, die es noch nicht gibt: sofort Block I1.
|
||||||
|
|
||||||
|
**A3. Schema stimmt** (inklusive `workersOffers.offerText` aus dem P4-Schritt; hast du P4 B6 eben erst mit dem Stand `558ce24` oder neuer von `workers-dev` gemacht und dort `Alles vorhanden.` gesehen, überspringst du A3; lief P4 B6 mit einem älteren Stand, fehlt `offerText` womöglich, und `Alles vorhanden.` von damals beweist nichts). Der Laufzeit-Schlüssel des Dienstes darf das nicht: Lege in der Appwrite-Konsole (Projekt `6a1058610003c5a13a05` → Integrationen → API-Schlüssel) einen Schlüssel `workers-setup` an (Ablauf 1 Tag, Bereiche `databases.read`, `collections.read/write`, `attributes.read/write`, `indexes.read/write`, nie `databases.write`, nicht in `.env`).
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps/workers-dev && ( read -rs -p 'Appwrite-Key workers-setup: ' APPWRITE_API_KEY; echo; export APPWRITE_API_KEY; timeout 300 node --max-old-space-size=1024 scripts/setup-collections.mjs --check; echo "Exit $?" )
|
||||||
|
```
|
||||||
|
*Erwartet:* 13 Zeilen `= workers…: N Attribute, M Indizes vorhanden` (darunter `= workersOffers: 14 Attribute, 2 Indizes vorhanden`), dann `Alles vorhanden.` und `Exit 0`. **Danach den Schlüssel in der Appwrite-Konsole löschen.** *Wenn nicht:* steht dort `- workersOffers.offerText FEHLT`, hol P4 B6 nach (nicht hier); 401 oder „missing scope“ = falscher Schlüssel; `NICHT BEREIT` = ein paar Minuten warten und wiederholen.
|
||||||
|
|
||||||
|
**A4. Das Abo ist freigegeben** (der Kaufweg verkauft das Hosting-Abo in der Fassung `2026-09-30-v5`; seine Bedingungen müssen online stehen). In WOMS → Rechtliches sind die Dokumente dieser Fassung hochgeladen (K01 hängt sie als PDF an). Dann:
|
||||||
|
```
|
||||||
|
curl -s -o /dev/null -w '%{http_code}\n' https://project.webklar.com/abo-bedingungen.html
|
||||||
|
```
|
||||||
|
*Erwartet:* `200`. *Wenn nicht:* nicht weitermachen; die Abo-Freigabe (Rechtliches hochladen) zuerst.
|
||||||
|
|
||||||
|
**A5. Compose-Grundlagen.** Der Dienst `kundenbereich-server` lässt Kunden mit Status `customer` an (sonst klappt die Anmeldung mit den K44-Daten nicht), und das Volume `workers_data` aus P1 steht in der Datei.
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && grep -c '^ - ALLOWED_CUSTOMER_STATUSES=.*customer' docker-compose.yml
|
||||||
|
```
|
||||||
|
*Erwartet:* `1`. Dann:
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && docker compose config --volumes | grep -x workers_data
|
||||||
|
```
|
||||||
|
*Erwartet:* `workers_data`. *Wenn nicht:* beim ersten Befehl `0` = `customer` in der Zeile `ALLOWED_CUSTOMER_STATUSES` ergänzen (heute `active,customer,lead`), Claude Bescheid; beim zweiten keine Ausgabe = P1 (Schritt 10) steht nicht in der Datei: abbrechen.
|
||||||
|
|
||||||
|
**A6. Traefik protokolliert nicht, und „entwurf“ ist nicht belegt.** Der Token steht im Pfad `/api/uebernahme/<Token>`; ein Zugriffsprotokoll würde ihn speichern.
|
||||||
|
```
|
||||||
|
docker inspect webklar-traefik --format '{{json .Args}}' | grep -ci accesslog
|
||||||
|
```
|
||||||
|
*Erwartet:* `0`. Die Vorschau-Adresse `entwurf.project.webklar.com` darf mit keinem Projekt oder Repo kollidieren (die Sperre in `server/services/reserviert.js` verhindert nur neue Zuordnungen):
|
||||||
|
```
|
||||||
|
docker exec -w /app webklar-kundenbereich-server node --max-old-space-size=256 --input-type=module -e "import {listDocuments,Query} from './server/services/appwriteAdmin.js'; import {config} from './server/config.js'; const d=await listDocuments(config.collections.websiteProjects,[Query.equal('subdomain','entwurf'),Query.limit(5)]); console.log('Projekte mit Subdomain entwurf:', d.length)"
|
||||||
|
```
|
||||||
|
*Erwartet:* `Projekte mit Subdomain entwurf: 0`. Dann:
|
||||||
|
```
|
||||||
|
ls /var/www/previews | grep -ix entwurf; ls /home/webklar/apps/traefik/dynamic | grep -i entwurf
|
||||||
|
```
|
||||||
|
*Erwartet:* die erste Abfrage ohne Treffer, die zweite höchstens `workers-entwurf.yml` (aus P4 C1). Dazu in Gitea unter `https://git.webklar.com/WEBklar` die Suche „entwurf“: kein Repo. *Wenn nicht:* bei `accesslog` über 0 den Zugriffslog aus Traefik nehmen (Claude), bei einem Treffer „entwurf“ das Projekt oder Repo umbenennen lassen (Claude), vorher nicht weitermachen.
|
||||||
|
|
||||||
|
**A7. Gitea kann mehrere Dateien in einem Commit** (der Commit „Erste Version der Website“ ersetzt die Vorlagendateien auf einmal).
|
||||||
|
```
|
||||||
|
curl -s https://git.webklar.com/swagger.v1.json | grep -c ChangeFilesOptions
|
||||||
|
```
|
||||||
|
*Erwartet:* mindestens `1`. *Wenn nicht:* Claude Bescheid, Gitea ist zu alt.
|
||||||
|
|
||||||
|
**A8. Andere Portal-Pushes abstimmen.** Kein Befehl. Warten noch andere Portal-Zweige auf den Push (etwa `feature/workers-datenschutz`, `auftraege/l7-rechtstexte`, `feature/uk-final-ui`), klärt ihr vor C, wer zuerst pusht; P5 wird in C1 ohnehin mit dem dann aktuellen `origin/main` zusammengeführt (Claude sieht dort, was auf `origin/main` neu ist). *Wenn nicht:* sag Claude, was schon gelandet ist.
|
||||||
|
|
||||||
|
### B. Sicherung und Konfiguration (Schalter bleibt aus)
|
||||||
|
|
||||||
|
**B1. Sicherungen** (mit Suffix `-workers-p5`, damit die von P1 und P4 bleiben; ein zweiter Anlauf am selben Tag nimmt `-workers-p5-2` (`-workers-p5b` und `-workers-p5c` gehören G3 und I1)).
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && cp -p .env .env.bak-$(date +%F)-workers-p5 && cp -p docker-compose.yml docker-compose.yml.bak-$(date +%F)-workers-p5 && ls -l .env.bak-$(date +%F)-workers-p5 docker-compose.yml.bak-$(date +%F)-workers-p5
|
||||||
|
```
|
||||||
|
*Erwartet:* zwei Dateien von heute, beide größer als 0. *Wenn nicht:* nichts ändern, bis beide da sind.
|
||||||
|
|
||||||
|
**B2. `.env`: Schalter `WORKERS_KAUF_ENABLED=off`** (nur der Wert `on` schaltet ein; ein zweiter Lauf legt nichts doppelt an). `WORKERS_VORSCHAU_ROOT` und `WORKERS_VORSCHAU_BASE` bekommen keine Zeile: die Vorgaben des Portals (`/workers-vorschau/vorschau` und `https://entwurf.project.webklar.com`) passen zum Mount aus B3 und zum Dienst.
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && [ "$(grep -c '^WORKERS_KAUF_ENABLED=' .env)" = 0 ] && printf '\nWORKERS_KAUF_ENABLED=off\n' >> .env; grep -c '^WORKERS_KAUF_ENABLED=off$' .env
|
||||||
|
```
|
||||||
|
*Erwartet:* `1`. *Wenn nicht:* `0` = die Zeile gab es schon mit anderem Wert: `sed -i 's/^WORKERS_KAUF_ENABLED=.*/WORKERS_KAUF_ENABLED=off/' .env` und die Zählung wiederholen.
|
||||||
|
|
||||||
|
**B3. Compose: drei neue Zeilen im Dienst `kundenbereich-server`** (ein Kommentar, die Umgebungsvariable und der Mount `workers_data` nur lesend). Der Befehl tut nichts, wenn `WORKERS_KAUF_ENABLED` schon in der Datei steht oder ein Anker nicht genau einmal vorkommt.
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && [ "$(grep -c WORKERS_KAUF_ENABLED docker-compose.yml)" = 0 ] && [ "$(grep -c '^ - ANALYTICS_URL=http://webklar-analytics:8091$' docker-compose.yml)" = 1 ] && [ "$(grep -c '^ - ./traefik/scripts/sync-preview-tls.sh:/sync-preview-tls.sh:ro$' docker-compose.yml)" = 1 ] && sed -i '/^ kundenbereich-server:$/,/^ [a-z0-9_-]*:$/{
|
||||||
|
\|^ - ANALYTICS_URL=http://webklar-analytics:8091$|a\
|
||||||
|
# Hustler-Kaufweg (2026-10-17): Seite "Website uebernehmen" zuerst aus; Vorschau-Dateien lesend aus dem Volume workers_data\
|
||||||
- WORKERS_KAUF_ENABLED=${WORKERS_KAUF_ENABLED:-off}
|
- WORKERS_KAUF_ENABLED=${WORKERS_KAUF_ENABLED:-off}
|
||||||
- WORKERS_VORSCHAU_ROOT=/workers-vorschau/vorschau
|
\|^ - ./traefik/scripts/sync-preview-tls.sh:/sync-preview-tls.sh:ro$|a\
|
||||||
```
|
|
||||||
```yaml
|
|
||||||
- workers_data:/workers-vorschau:ro
|
- workers_data:/workers-vorschau:ro
|
||||||
```
|
}' docker-compose.yml
|
||||||
Das Volume `workers_data` steht seit P1 oben im Block `volumes:` der Datei. Prüfung: `docker compose config -q && echo gueltig`. Das Volume wird im Portal **nur lesend** gemountet (`:ro`); damit der Portal-Container die Dateien trotzdem lesen kann, legt P4 Verzeichnisse mit 0755 und Dateien mit 0644 an (Übersicht §9, A14). Prüfung nach dem Start (sobald ein Vorschau-Ordner `<T>` im Volume liegt, spätestens in Schritt 9.1): `docker exec webklar-kundenbereich-server ls -l /workers-vorschau/vorschau/<T>/` zeigt `-rw-r--r--` bei den Dateien (Unterordner `drwxr-xr-x`) und keine Meldung „Permission denied“.
|
```
|
||||||
5. **Portal-Container neu anlegen** (nur dieser Dienst): `docker compose up -d --no-deps kundenbereich-server`. Prüfung nach etwa einer Minute (der Start installiert Pakete): `docker exec webklar-kundenbereich-server sh -c 'printenv WORKERS_KAUF_ENABLED; ls /workers-vorschau'` zeigt `off` und den Inhalt des Volumes; `curl -s -o /dev/null -w '%{http_code}\n' https://project.webklar.com/api/health` zeigt 200.
|
Prüfen:
|
||||||
6. **Voraussetzungen prüfen** (einmalig, vor dem Livegang): (a) Gitea kann mehrere Dateien in einem Commit: `curl -s https://git.webklar.com/swagger.v1.json | rg -c 'ChangeFilesOptions'` zeigt mindestens 1. (b) Der bestehende Stripe-Webhook-Endpunkt `https://project.webklar.com/api/abo/webhook` liefert `checkout.session.completed` und `customer.subscription.created|updated|deleted` (Stripe-Dashboard, Entwickler, Webhooks); es ist nichts umzustellen, der Kaufweg nutzt denselben Endpunkt.
|
```
|
||||||
7. **Code live bringen.** `cd /home/webklar/apps/kundenbereich.webklar.com/dev-workers-kauf && git fetch origin && git rebase origin/main && timeout 900 node --max-old-space-size=1024 --test test/aboRoutes.test.js` (grün), dann `git push origin feature/workers-kaufweg:main` (Deploy-Hook baut und startet das Portal neu). Prüfung: `curl -s -o /dev/null -w '%{http_code}\n' https://project.webklar.com/api/uebernahme/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA` zeigt 404; `curl -sI https://project.webklar.com/uebernahme.html | head -1` zeigt 200; Serverpuls bleibt grün.
|
cd /home/webklar/apps && docker compose config -q && diff docker-compose.yml.bak-$(date +%F)-workers-p5 docker-compose.yml | grep -c '^>'
|
||||||
8. **Rechtsrunde (E9).** Vor dem Schalter: die neuen Wortlaute freigeben (Kundenart-Auswahl, Stripe-Hinweis „… auf der Seite „Website übernehmen“ …“, Zitat des Angebotssatzes) und prüfen, dass `workersSettings/main.offerText` denselben Preis nennt wie der Katalog (20 € im Monat, 12 Monate). Ohne deine Freigabe bleibt der Schalter auf `off`.
|
```
|
||||||
9. **Manuelle Prüfung mit einem Testkonto** (Stripe live; die Adresse muss in `ABO_TEST_EMAILS` stehen, dann kostet das Abo 0,50 €; wähle „nach Ablauf der Widerrufsfrist“, dann wird heute nichts abgebucht):
|
*Erwartet:* keine Fehlermeldung von `config`, dann `3`. *Wenn nicht:* keine Änderung und Exit 1 beim ersten Befehl = schon eingefügt oder ein Anker fehlt (die Datei wurde umgebaut): nichts erzwingen, Claude Bescheid; eine andere Zahl oder ein Fehler von `config` = `cp -p docker-compose.yml.bak-<Datum>-workers-p5 docker-compose.yml` und Claude Bescheid.
|
||||||
1. Vorschau-Ordner im Volume anlegen: `T=$(head -c 32 /dev/urandom | base64 | tr '+/' '-_' | tr -d '=') && docker exec webklar-workers sh -c "mkdir -p /data/vorschau/$T && echo '<h1>Test</h1>' > /data/vorschau/$T/index.html" && echo $T`. Leserechte des Portals prüfen (Schritt 4): `docker exec webklar-kundenbereich-server ls -l /workers-vorschau/vorschau/$T/` zeigt `-rw-r--r-- … index.html`.
|
|
||||||
2. `.env`: `WORKERS_KAUF_ENABLED=on`, dann `docker compose up -d --no-deps kundenbereich-server`.
|
**B4. Portal-Container neu anlegen** (nur dieser Dienst; 1 bis 2 Minuten bis zur Antwort).
|
||||||
3. Testangebot: `docker exec -w /app webklar-kundenbereich-server timeout 60 node --max-old-space-size=256 scripts/workers-kauf-testangebot.mjs --email <testadresse> --firma "Testbetrieb GmbH" --vorschau $T` – der Befehl druckt den Link und die Angebots-ID.
|
```
|
||||||
4. Link auf Handy und am Desktop in einem privaten Fenster öffnen (eine offene Kundenansicht im Browser sperrt das Bestellen mit 403); Seite prüfen (Betrieb, Vorschau-Link, Angebotssatz, Bedingungen, keine waagerechte Scrollleiste bei 360 px Breite, Dunkelmodus lesbar), bestellen, bei Stripe bezahlen.
|
cd /home/webklar/apps && docker compose up -d --no-deps kundenbereich-server
|
||||||
5. Prüfung: Rücksprung auf „Vielen Dank“; in Stripe tragen Kunde und Abo `webklarCustomerId`; im WOMS gibt es den Kunden (Status `customer`) mit Projekt „Testbetrieb GmbH“ (Vorschau erreichbar nach Anmeldung); Postfach der Testadresse: K44 (mit Passwort) und K01; Team-Postfach: T02 und kein Hinweis mit Betreff „[Hustler] …“; `workersOffers.status = bezahlt`; Anmeldung mit den K44-Daten klappt; Passwort im WOMS-Kundenblatt leer.
|
```
|
||||||
6. Aufräumen: Abo bei Stripe beenden bzw. widerrufen; Kunde im WOMS löschen; `docker exec -w /app webklar-kundenbereich-server timeout 60 node --max-old-space-size=256 scripts/workers-kauf-testangebot.mjs --loeschen <angebotsId>` (löscht nur Angebote, deren ID mit `t-` beginnt); Test-Repo `WEBklar/testbetrieb-gmbh` im WOMS-Projekt archivieren; Ordner `docker exec webklar-workers rm -r -- /data/vorschau/$T`.
|
*Erwartet:* `Recreated`/`Started` für `kundenbereich-server`. *Wenn nicht:* Fehlermeldung an Claude, die Sicherungen aus B1 liegen bereit.
|
||||||
10. **Handfälle** (kommen per T25-Mail, Betreff „[Hustler] …“): *E-Mail gehört schon einem Kunden* – im Stripe-Dashboard am Abo und am Kunden `webklarCustomerId=<ID des bestehenden Kunden>` setzen, dann das Ereignis `checkout.session.completed` erneut senden: der Handler setzt am Kunden fort (Nachweis, Projekt, Abschluss). Hat dieser Kunde im WOMS noch ein gespeichertes Anfangspasswort, geht es per K44 an die im Kundendokument hinterlegte Adresse (nicht an die Käufer-Adresse); sonst entfällt K44. Prüfe vorher, dass der Kunde wirklich dieselbe Person ist. *Doppelte Zahlung* – die zweite Zahlung bei Stripe erstatten und das zweite Abo beenden. *Vorschau nicht übernommen* – Dateien prüfen, Ereignis erneut senden.
|
|
||||||
11. **Zurücknehmen.** `WORKERS_KAUF_ENABLED=off` in `.env`, `docker compose up -d --no-deps kundenbereich-server`; die Links antworten sofort 404. Der Webhook-Zweig bleibt wirksam (bezahlte Sitzungen werden weiter verarbeitet).
|
**B5. Umgebung prüfen** (nach 1 bis 2 Minuten; zeigt nur unkritische Werte).
|
||||||
|
```
|
||||||
|
docker exec webklar-kundenbereich-server sh -c 'printenv WORKERS_KAUF_ENABLED ALLOWED_CUSTOMER_STATUSES; ls /workers-vorschau; node -v'
|
||||||
|
```
|
||||||
|
*Erwartet:* `off`, eine Liste mit `customer`, ein Eintrag `vorschau` (und `imap-state.json`), `v20.…`. Dann:
|
||||||
|
```
|
||||||
|
curl -s -o /dev/null -w '%{http_code}\n' https://project.webklar.com/api/health
|
||||||
|
```
|
||||||
|
*Erwartet:* `200`. *Wenn nicht:* `ls: /workers-vorschau: No such file` = der Mount fehlt (B3); `WORKERS_KAUF_ENABLED` leer = B2 oder die Compose-Zeile fehlt; kein `200` nach 3 Minuten = `docker logs --tail 30 webklar-kundenbereich-server` an Claude.
|
||||||
|
|
||||||
|
### C. Code live (Schalter weiter aus)
|
||||||
|
|
||||||
|
**C1. [Claude] Zweig mit `origin/main` zusammenführen und prüfen.** Sag Claude: „Kaufweg-Zweig mit origin/main zusammenführen und prüfen.“ Claude arbeitet im Ordner `/home/webklar/apps/kundenbereich.webklar.com/dev-workers-kauf` (Zweig `feature/workers-kaufweg`): `git fetch origin`, `git log --oneline HEAD..origin/main`, dann `git merge origin/main` (nie `--force`; Konflikte sind in `server/config.js`, `server/index.js`, `server/routes/abo.js`, `test/support/schema.js` und in der Liste von `test/mailTemplates.test.js` zu erwarten, falls neue Mail-Nummern dazukamen). Er prüft, dass `K44 Website übernommen.html` und `T25 Workers Hinweis.html` auf keinem anderen Zweig belegt sind und dass `git diff dd062b1 origin/main --stat -- public/app.js public/abo-bedingungen.html server/services/products.js server/services/bestelltexte.js server/config.js` leer ist (sonst hat sich der Wortlaut der Abo-Seite geändert und die Fixture `test/support/wortlautAboV5.json` muss neu geprüft werden). Er trägt außerdem im Ordner `/home/webklar/apps/kundenbereich.webklar.com/dev-kundenansicht/docs/superpowers/plans` ein: in `2026-10-16-kundenansicht-00-uebersicht.md` (Zeile zu P6 „K44 und T25 frei (N29)“, den Absatz zu den Mailkennungen mit „K44 und T25 bleiben frei“ und die Stelle „K40, K41 und T25 bleiben frei für P6“), dass K44 und T25 durch Workers P5 belegt sind; in `2026-10-16-kundenansicht-11-umschalten-aufraeumen.md`, Teil C „Fassung 6 festschreiben“, eine Kaufweg-Zeile: erst sperren (`WORKERS_KAUF_ENABLED=off`, `WORKERS_KAUFLINK=nein`), dann `server/services/workersKaufTexte.js` und `test/support/wortlautAboV5.json` auf Fassung 6 umstellen. Dann der Gesamtlauf bei `free -m` „available“ über 2500: `timeout 1800 node --max-old-space-size=1024 --test --test-concurrency=2 test/*.test.js`. *Erwartet:* Merge ohne offenen Konflikt, `# fail 0`, höchstens der eine bekannte Skip („PDFs der Fassung 6“; Stand vor dem Merge: 7696 Tests, 7695 grün). Fällt `previewDevRouter.test.js` allein, wird sie einzeln wiederholt (bekannter Flake). *Wenn nicht:* du pushst nichts, bevor alles grün ist.
|
||||||
|
|
||||||
|
**C2. Pushen** (Deploy-Hook baut und startet das Portal neu; wieder 1 bis 2 Minuten ohne Portal).
|
||||||
|
```
|
||||||
|
git -C /home/webklar/apps/kundenbereich.webklar.com/dev-workers-kauf push origin feature/workers-kaufweg:main
|
||||||
|
```
|
||||||
|
*Erwartet:* Push angenommen. *Wenn nicht:* „non-fast-forward“ = `main` hat neue Commits: nie erzwingen, C1 wiederholen.
|
||||||
|
|
||||||
|
**C3. Die Route gibt es und ist neutral** (Schalter aus; geprüft wird Body und Kopf, denn auch eine alte Fassung antwortet 404, nur mit der HTML-Seite „Cannot GET“).
|
||||||
|
```
|
||||||
|
curl -si "https://project.webklar.com/api/uebernahme/$(printf 'A%.0s' $(seq 43))"
|
||||||
|
```
|
||||||
|
*Erwartet:* `HTTP/2 404`, Kopfzeilen `cache-control: no-store`, `x-robots-tag: noindex`, `x-content-type-options: nosniff`, `referrer-policy: no-referrer`, Body `{"error":"Dieser Link ist nicht mehr gültig."}`. Dann dasselbe als Bestellung:
|
||||||
|
```
|
||||||
|
curl -s -X POST -H 'Content-Type: application/json' -d '{}' "https://project.webklar.com/api/uebernahme/$(printf 'A%.0s' $(seq 43))/checkout"
|
||||||
|
```
|
||||||
|
*Erwartet:* derselbe Body. *Wenn nicht:* HTML „Cannot GET“ = der Deploy ist noch nicht durch oder fehlgeschlagen (3 Minuten warten, dann `docker logs --tail 30 webklar-kundenbereich-server`); Serverpuls muss grün bleiben.
|
||||||
|
|
||||||
|
**C4. Seite und Dateien sind da, ohne interne Namen.**
|
||||||
|
```
|
||||||
|
for f in uebernahme.html uebernahme.css uebernahme.js uebernahme-ansicht.js auftrag.css auftrag-ansicht.js auftrag-dom.js; do curl -s -o /dev/null -w "$f %{http_code} %{content_type}\n" https://project.webklar.com/$f; done
|
||||||
|
```
|
||||||
|
*Erwartet:* sieben Mal `200`, die `.js` mit einem JavaScript-Typ (Modulskripte brauchen ihn). Dann:
|
||||||
|
```
|
||||||
|
curl -s https://project.webklar.com/uebernahme.html https://project.webklar.com/uebernahme.js https://project.webklar.com/uebernahme-ansicht.js | grep -ciE 'hustler|workers'
|
||||||
|
```
|
||||||
|
*Erwartet:* `2` (beide Treffer sind Dateinamen in einem Code-Kommentar von `uebernahme-ansicht.js`, kein Text, den ein Käufer sieht). *Wenn nicht:* ein Treffer mehr, der sichtbarer Text ist = Claude Bescheid; der Käufer darf „Hustler“ und „Workers“ nie lesen.
|
||||||
|
|
||||||
|
**C5. Node 20 lehnt Symlinks beim Lesen ab** (die Vorschau-Dateien werden mit `O_NOFOLLOW` gelesen; getestet war das bisher nur auf Node 24).
|
||||||
|
```
|
||||||
|
docker exec -w /app webklar-kundenbereich-server timeout 120 node --max-old-space-size=256 --test test/workersVorschau.test.js
|
||||||
|
```
|
||||||
|
*Erwartet:* `# fail 0` (darunter der Test mit dem Code `ELOOP`). *Wenn nicht:* Ausgabe an Claude, der Kaufweg bleibt aus.
|
||||||
|
|
||||||
|
**C6. Die Begrenzung hängt an der echten Adresse** (einmal; verbraucht die 10 Bestellversuche pro Stunde deiner Adresse, also aus einem anderen Netz als die Testbestellung oder eine Stunde Abstand; jeder Versuch nimmt einen anderen Token, damit nur die Adressbegrenzung wirkt).
|
||||||
|
```
|
||||||
|
for i in $(seq 1 12); do curl -s -o /dev/null -w '%{http_code} ' -X POST -H "X-Forwarded-For: 198.51.100.$i" -H 'Content-Type: application/json' -d '{}' "https://project.webklar.com/api/uebernahme/$(head -c 48 /dev/urandom | base64 -w0 | tr -d '+/=' | head -c 43)/checkout"; done; echo
|
||||||
|
```
|
||||||
|
*Erwartet:* neun Mal `404`, dann `429 429 429` (der POST aus C3 zählt schon zu den 10 Versuchen deiner Adresse; liegt C3 mehr als eine Stunde zurück oder kam aus einer anderen Adresse, sind es zehn Mal `404`, dann `429 429`). *Wenn nicht:* zwölf Mal `404` heißt, die Anwendung sieht eine fälschbare Adresse und die Begrenzung bremst nichts: Schalter bleibt aus, Claude Bescheid.
|
||||||
|
|
||||||
|
**C7. Felder in der echten Datenbank** (die Tests laufen gegen einen Nachbau, der `customers`, `customerPortalAccess` und `websiteProjects` nicht prüft; fehlt ein Feld, scheitert der erste echte Kauf erst nach der Zahlung).
|
||||||
|
```
|
||||||
|
docker exec -w /app webklar-kundenbereich-server node --max-old-space-size=256 scripts/kundenansicht-setup.mjs --pruefen
|
||||||
|
```
|
||||||
|
*Erwartet:* `Alles vorhanden.`, Exit 0 (`featureConsents.kundenart` besteht). Sieh außerdem in der Appwrite-Konsole nach: `customers` hat `street`, `postalCode`, `city`, `country`, `companyName`, `kundenart`, `customerStatus` und `portalPassword`; `featureConsents` hat `stripeSessionId` mit dem Index `idx_session`. *Wenn nicht:* `FEHLT`-Zeilen oder ein fehlendes Feld: nichts selbst anlegen, Claude Bescheid; bei 401 darf der Schlüssel des Portals das Schema nicht lesen, dann nur in der Konsole prüfen.
|
||||||
|
|
||||||
|
### D. Stripe vorbereiten
|
||||||
|
|
||||||
|
**D1. Ereignis `checkout.session.expired` freischalten** (Stripe-Dashboard → Entwickler → Webhooks → Endpunkt `https://project.webklar.com/api/abo/webhook` → Ereignisse bearbeiten; die bisherigen bleiben). Kein Befehl. *Erwartet:* in der Liste stehen jetzt auch `checkout.session.expired` neben `checkout.session.completed`, `customer.subscription.*`, `invoice.paid` und `invoice.payment_failed`. *Wenn nicht:* ohne dieses Ereignis bleibt eine abgebrochene Bestellung im Status `checkout` (der Link bleibt bis `expiresAt` benutzbar), bis das nächtliche Aufräumen des Dienstes sie auf `abgelaufen` setzt; das geschieht frühestens `expiresAt` plus 3 Tage und nur, wenn die Vorschau selbst fällig ist. Der Stripe-Kunde mit Name und Anschrift des Käufers bleibt bis zum Tagesjob (D3) stehen, der ihn nach 30 Tagen löscht.
|
||||||
|
|
||||||
|
**D2. Schlüsseltyp des Portals** (zeigt nur die ersten 8 Zeichen, den Typ, nie den Schlüssel).
|
||||||
|
```
|
||||||
|
docker exec webklar-kundenbereich-server sh -c 'printenv STRIPE_SECRET_KEY | cut -c1-8'
|
||||||
|
```
|
||||||
|
*Erwartet:* `sk_live_` (voller Zugriff, nichts weiter zu tun). *Wenn nicht:* `rk_live_` = eingeschränkter Schlüssel: Stripe-Dashboard → Entwickler → API-Schlüssel → Schlüssel bearbeiten, dort Kunden auf „Schreiben“ (das Löschen gehört dazu) und Abos auf „Lesen“; der Tagesjob, der abgebrochene Bestellungen aufräumt, braucht beides, sonst zählt er nur Fehler. Eine Zeile mit `_test_` = das Portal läuft gegen den Stripe-Testmodus: Claude Bescheid, dann stimmen die Preise und Schritte in E nicht.
|
||||||
|
|
||||||
|
**D3. Aufräum-Skript einmal im Trockenlauf.** Das Portal löscht täglich (230 Sekunden nach jedem Start, danach alle 24 Stunden, unabhängig vom Schalter) die Stripe-Kunden abgebrochener Kaufweg-Bestellungen, die älter als 30 Tage sind, nie ein Abo hatten und zu keinem bezahlten Angebot gehören (höchstens 50 je Lauf). Das Skript zeigt, was gelöscht würde, und ändert ohne `--ausfuehren` nichts.
|
||||||
|
```
|
||||||
|
docker exec -w /app webklar-kundenbereich-server timeout 120 node --max-old-space-size=256 scripts/workers-kauf-stripe-aufraeumen.mjs
|
||||||
|
```
|
||||||
|
*Erwartet:* `Trockenlauf: es wird nichts gelöscht.`, dann `gelesen=N kandidaten=0 gelöscht=0 würde-löschen=0 behalten=0 schon-weg=0 fehler=0`, Exit 0 (vor der ersten Bestellung gibt es keine Kandidaten). *Wenn nicht:* `Stripe ist nicht eingerichtet` = `STRIPE_SECRET_KEY` fehlt; `fehler=` über 0 oder `Fehler: … status=403` = der Schlüssel darf nicht lesen oder löschen (D2).
|
||||||
|
|
||||||
|
### E. Testbestellung (zweimal; der Schalter ist dafür kurz an)
|
||||||
|
|
||||||
|
Lauf 1: Kundenart Unternehmen, Start „nach Ablauf der Widerrufsfrist“ (heute wird nichts abgebucht), mit der „Zurück“-Probe. Lauf 2: Kundenart privat, Start „sofort“ (0,50 € werden wirklich abgebucht). Die beiden Testadressen sind Aliase deiner eigenen Adresse; sie dürfen keinem Kunden und keinem Mitarbeiter in WOMS gehören (sonst antwortet die Seite mit 409).
|
||||||
|
|
||||||
|
**E1. Testadressen für den Testpreis eintragen** (nur Adressen aus `ABO_TEST_EMAILS` zahlen 0,50 € statt 20 €; der Befehl ist wiederholbar und hängt nur an, was fehlt).
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && for A in kenso.gri+kauftest1@gmail.com kenso.gri+kauftest2@gmail.com; do if grep -q "^ABO_TEST_EMAILS=.*$A" .env; then :; elif [ "$(grep -c '^ABO_TEST_EMAILS=' .env)" = 0 ]; then printf '\nABO_TEST_EMAILS=%s\n' "$A" >> .env; else sed -i "s/^ABO_TEST_EMAILS=\(.*\)\$/ABO_TEST_EMAILS=\1,$A/" .env; fi; done; grep -c '^ABO_TEST_EMAILS=.*kauftest1.*kauftest2' .env
|
||||||
|
```
|
||||||
|
*Erwartet:* `1`. *Wenn nicht:* steht die Zeile in Anführungszeichen, ergänzt du die beiden Adressen im Editor (kleingeschrieben, durch Komma getrennt).
|
||||||
|
|
||||||
|
**E2. Schalter an (nur für den Test) und Neustart.**
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && sed -i 's/^WORKERS_KAUF_ENABLED=.*/WORKERS_KAUF_ENABLED=on/' .env && grep -c '^WORKERS_KAUF_ENABLED=on$' .env
|
||||||
|
```
|
||||||
|
*Erwartet:* `1`. Dann:
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && docker compose up -d --no-deps kundenbereich-server
|
||||||
|
```
|
||||||
|
Nach 1 bis 2 Minuten `docker exec webklar-kundenbereich-server printenv WORKERS_KAUF_ENABLED` (*Erwartet:* `on`). Der Kauf-Link des Dienstes bleibt `nein`: es gibt keinen echten Link, nur die Testangebote aus E5 und E12.
|
||||||
|
|
||||||
|
**E3. Vorschau-Ordner im Dienst anlegen.** Der Token ist 43 Zeichen lang und beginnt nie mit `-` oder `_` (das Skript lehnt solche ab); der Ordner liegt im Dienst unter `/data/vorschau/<Token>` und im Portal unter `/workers-vorschau/vorschau/<Token>`. Schreib den Token auf (Lauf 2 nimmt `T2` genauso).
|
||||||
|
```
|
||||||
|
T=$(head -c 48 /dev/urandom | base64 -w0 | tr -d '+/=' | head -c 43); echo "$T"; printf %s "$T" | wc -c
|
||||||
|
```
|
||||||
|
*Erwartet:* ein Token und `43`. Dann:
|
||||||
|
```
|
||||||
|
docker exec webklar-workers sh -c "mkdir -p /data/vorschau/$T && echo '<h1>Test</h1>' > /data/vorschau/$T/index.html"
|
||||||
|
```
|
||||||
|
Dann prüfst du, dass das Portal die Datei lesen kann:
|
||||||
|
```
|
||||||
|
docker exec webklar-kundenbereich-server ls -l /workers-vorschau/vorschau/$T/
|
||||||
|
```
|
||||||
|
*Erwartet:* `-rw-r--r-- … index.html`. *Wenn nicht:* `No such file` = der Ordner liegt nicht im Volume (E3 wiederholen) oder der Mount fehlt (B3); `Permission denied` = Claude Bescheid.
|
||||||
|
|
||||||
|
**E4. Trockenlauf ohne Geld: Testangebot anlegen und wieder löschen.** Das Skript legt dieselben drei Dokumente an wie der Dienst (Interessent, Vorschau, Angebot) und nimmt den Angebotssatz aus den Einstellungen des Hustlers (WOMS → Hustler → Hochhaus → Einstellungen); es legt nichts an, wenn die Adresse nicht in `ABO_TEST_EMAILS` steht.
|
||||||
|
```
|
||||||
|
docker exec -w /app webklar-kundenbereich-server timeout 60 node --max-old-space-size=256 scripts/workers-kauf-testangebot.mjs --email kenso.gri+kauftest1@gmail.com --firma "Testbetrieb GmbH" --vorschau "$T"
|
||||||
|
```
|
||||||
|
*Erwartet:* `Testangebot angelegt.`, `Kauf-Link: https://project.webklar.com/uebernahme.html#t=<43 Zeichen>`, `Angebot: t-<8 Zeichen>`, `Angebotssatz (Einstellungen): …` (oder `(Voreinstellung)`), Exit 0. Öffne den Link in einem privaten Fenster: die Seite lädt mit „Testbetrieb GmbH“. Dann löschst du das Angebot (`t-XXXXXXXX` durch die Kennung aus der Zeile `Angebot:` ersetzen):
|
||||||
|
```
|
||||||
|
docker exec -w /app webklar-kundenbereich-server timeout 60 node --max-old-space-size=256 scripts/workers-kauf-testangebot.mjs --loeschen t-XXXXXXXX
|
||||||
|
```
|
||||||
|
*Erwartet:* Zeilen `gelöscht: workersOffers t-…`, `gelöscht: workersPreviews …`, `gelöscht: workersProspects t-…`, Exit 0; der Link zeigt jetzt „Dieser Link ist nicht mehr gültig.“. Das prüft echte Appwrite-Kennungen und Löschrechte ohne Stripe. *Wenn nicht:* `steht nicht in ABO_TEST_EMAILS … Nichts angelegt.` = E1 und E2 (inklusive Neustart); `Zu diesem Vorschau-Token gibt es schon ein Vorschau-Dokument` = frischen Token nehmen; `Fehler: … (Status 400)` mit „Unknown attribute“ = A3; meldet `--loeschen` bei einem frisch angelegten Angebot „kein Testangebot dieses Skripts“, hat das Skript einen Fehler: Claude Bescheid.
|
||||||
|
|
||||||
|
**E5. Echtes Testangebot für Lauf 1.** Derselbe Befehl wie in E4 (Ordner und Token bleiben gültig). Schreib die Zeile `Angebot:` auf. *Erwartet:* wie in E4.
|
||||||
|
|
||||||
|
**E6. Bestellen (Lauf 1).** Öffne den Link in einem **privaten Fenster** (eine geöffnete Kundenansicht des WOMS im selben Browser sperrt das Bestellen mit 403), am Handy und am Desktop.
|
||||||
|
- Sieh dir die Seite an: Betrieb „Testbetrieb GmbH“, Link zur Vorschau, Angebotssatz, Leistungsbeschreibung, Tabelle „Ihre Bestellung“ (die Zeilen „Fällig“ und „Leistungsbeginn“ wechseln mit der Startwahl), der Knopf „Zahlungspflichtig bestellen“ direkt unter Tabelle und Mängelhaftungs-Hinweis. Bei 360 Pixel Breite keine waagerechte Leiste, im Dunkelmodus lesbar; die Links der Zustimmung öffnen `/abo-bedingungen.html`, `/abo-bedingungen.html#widerruf` und `/datenschutz.html` in neuem Tab.
|
||||||
|
- Lass ein Pflichtfeld leer und klicke bestellen: das erste fehlerhafte Feld wird markiert und angesprungen.
|
||||||
|
- **Konto-Probe:** Fülle das Formular sonst vollständig aus (Haken und Startwahl gesetzt) und trage einmal die E-Mail-Adresse eines Teammitglieds (aus WOMS) und einmal die eines bestehenden Kunden in anderer Groß-/Kleinschreibung ein. *Erwartet:* jeweils der rote Satz „Zu dieser E-Mail-Adresse gibt es schon ein Kundenkonto …“ über dem Knopf, das Formular bleibt gefüllt, der Knopf ist frei, und in Stripe entsteht kein neuer Kunde. *Wenn nicht:* der Satz „Das hat gerade nicht geklappt“ = `docker logs --since 10m webklar-kundenbereich-server 2>&1 | grep '\[uebernahme\]'` an Claude (Appwrite 1.8.1 verlangt dann womöglich einen Index auf `employees.email`).
|
||||||
|
- **Tastatur:** Gehe nur mit Tab, Pfeiltasten und Leertaste durch das Formular. *Erwartet:* die Reihenfolge Kundenart, Name, E-Mail, Betrieb (nur bei Unternehmen), Straße, PLZ, Ort, Haken (danach die Links der Zustimmung), Startwahl, Knopf; bei einem Fehler sagt ein Screenreader „Bitte prüfen Sie die markierten Angaben.“ an (einmal mit VoiceOver oder TalkBack probieren). *Wenn nicht:* eine andere Reihenfolge oder ein Feld, das sich nicht erreichen lässt = Claude Bescheid.
|
||||||
|
- Dann Kundenart **Unternehmen**, Name, Adresse `kenso.gri+kauftest1@gmail.com`, Anschrift, Betrieb „Testbetrieb GmbH“ lassen, Haken, Start **nach Ablauf der Widerrufsfrist**, bestellen. *Erwartet:* Weiterleitung zur Stripe-Kasse mit dem Hinweis über dem Knopf („Ihre Bestellung haben Sie auf der Seite „Website übernehmen“ abgegeben …“) und dem Testpreis 0,50 € statt 20 € im Monat.
|
||||||
|
- **Zurück-Probe:** klicke bei Stripe im Browser zurück. *Erwartet:* die Seite zeigt das Formular wieder (mit deinen Eingaben, wenn der Browser die Seite zwischengespeichert hat, sonst leer), der Bestellknopf ist wieder frei, der Link ist derselbe.
|
||||||
|
- Bestelle noch einmal und bezahle mit einer echten Karte (heute wird nichts abgebucht). *Erwartet:* Rücksprung auf die Seite mit „Vielen Dank“ (Adresse `…/uebernahme.html?danke=1`), die zwei E-Mails ankündigt.
|
||||||
|
|
||||||
|
*Wenn nicht:* „Die Online-Bestellung ist noch nicht freigeschaltet.“ = Stripe im Portal nicht eingerichtet (D2); ein roter Satz am Feld oder über dem Knopf nennt den Grund; 409 = die Adresse gehört einem Kunden oder Mitarbeiter, nimm einen anderen Alias; alles andere an Claude.
|
||||||
|
|
||||||
|
**E7. Postfächer und Anmeldung.** Kein Befehl. *Erwartet im Postfach der Testadresse:* zuerst K01 (Vertragsbestätigung mit den PDF-Anhängen, Fassung `2026-09-30-v5`), dann K44 („Website übernommen“: Absender WEBklar, Passwort mit 16 Zeichen, Projektadresse, der Block zum Leistungsbeginn passt zur Wahl „nach Ablauf der Widerrufsfrist“ mit dem Datum, nirgends „Hustler“ oder „Workers“); sieh beide in Gmail hell und dunkel am Handy an. *Im Team-Postfach:* T02 („Neues Abo“), keine Kopie von K44 (kein Passwort im Team-Postfach) und keine Mail mit „[Hustler]“ im Betreff. Melde dich unter `https://project.webklar.com/login.html` mit den K44-Daten an: *Erwartet:* das Projekt „Testbetrieb GmbH“ erscheint, unter „Änderungen“ steht nur „Erste Version der Website“ (nirgends „Hustler“); nach ein paar Minuten zeigt `https://testbetrieb-gmbh.project.webklar.com` nach der Anmeldung die Seite „Test“. *Wenn nicht:* keine K44 nach 5 Minuten oder eine Mail „[Hustler] …“ = H3 (die Mail nennt den Grund); Anmeldung scheitert trotz richtigem Passwort = `customer` fehlt in `ALLOWED_CUSTOMER_STATUSES` (A5).
|
||||||
|
|
||||||
|
**E8. Daten in Appwrite ansehen** (Appwrite-Konsole → Datenbank `woms-database`; das ist die einzige Prüfung gegen die echten Sammlungen, die Tests laufen gegen einen Nachbau). *Erwartet:* `customers`: ein Dokument mit `customerStatus` = `customer`, `street`, `postalCode`, `city`, `country` = `Deutschland`, `companyName` = `Testbetrieb GmbH`, `kundenart` = `unternehmen` und leerem `portalPassword`; dazu ein Dokument in `customerPortalAccess` und eines in `websiteProjects` (Name „Testbetrieb GmbH“, Repo `WEBklar/testbetrieb-gmbh`, `customerId` des Kunden). `featureConsents`: genau ein Dokument mit `stripeSessionId` = der bezahlten Sitzung (`cs_…`), `kundenart`, sechs Einträgen in `texte` (der erste ist der Angebotssatz), `fassungen` mit `zustimmung` = `2026-09-30-v5`, `quelle` = `workers-uebernahme`, und einer IP, die deine öffentliche Adresse ist (keine 172.x). `workersOffers`: `status` = `bezahlt`, `paidAt` mit Z am Ende, `customerId`, `stripeSessionId`. `workersProspects`: `status` = `deal`. `workersEvents` (die neuesten Einträge): ein Ereignis mit Id `deal-…` („Deal: Testbetrieb GmbH hat die Website übernommen (Hosting-Abo)“), keines mit `fehler-…`. *Wenn nicht:* ein fehlendes oder falsch benanntes Feld (etwa `featureConsents.kundenart`, `customers.street`) macht sich als Hinweis „[Hustler] …“ bemerkbar (H3): C7 wiederholen und Claude Bescheid sagen.
|
||||||
|
|
||||||
|
**E9. Stripe und Zustellung ansehen** (Dashboard). *Erwartet:* genau ein Kunde mit der Testadresse; Kunde und Abo tragen `webklarCustomerId` (die Kunden-Id aus E8), der Kunde auch `workersOfferId`; die erste Sitzung der Zurück-Probe steht auf „abgelaufen“. Die Checkout-Sitzung trägt in den Metadaten nur `workersOfferId` und `productKey`; `success_url` enthält keinen Token, `cancel_url` den Token nur nach `#` (Entwickler → Protokolle, Aufruf `POST /v1/checkout/sessions`). Unter Entwickler → Webhooks → Endpunkt: alle Zustellungen (`checkout.session.completed`, `checkout.session.expired` der ersten Sitzung, `customer.subscription.created`, `invoice.paid`) mit Antwort `200`, keine `500`, und `checkout.session.completed` mit **einem** Zustellversuch. Notiere den Abstand zwischen der Zahlung und der Uhrzeit der Mail K44: das ist ungefähr die Dauer des Handlers (Repo anlegen, Dateien, zwei Mails laufen darin). Dauert er länger, als Stripe wartet, steht in der Liste ein zweiter Zustellversuch; das ist abgefangen (eine Sperre je Angebot: kein zweites Konto, keine zweite Mail), aber der Hinweis „Doppelte Zahlung für ein Angebot“ darf dann nicht kommen. *Wenn nicht:* eine `500` an einer dieser Zustellungen oder mehrere Kunden = Claude Bescheid, bevor es weitergeht.
|
||||||
|
|
||||||
|
**E10. Logs sind sauber** (gleich nach dem Test; kein Token, keine Adresse, keine Fehlerzeile der Bestellung).
|
||||||
|
```
|
||||||
|
docker logs --since 1h webklar-kundenbereich-server 2>&1 | grep -c -e "$(printf %s "$T" | cut -c1-12)" -e 'kauftest' -e 'Bestellung fehlgeschlagen'
|
||||||
|
```
|
||||||
|
*Erwartet:* `0`. *Wenn nicht:* melde die Zeile Claude und gib sie sonst niemandem weiter (sie kann Daten enthalten).
|
||||||
|
|
||||||
|
**E10a. Voller Preis ohne Rabatt (optional, einmal, ohne zu zahlen).** Der Testpreis hängt nur an der Adresse des Angebots, nie an der Eingabe des Käufers. Neuer Token `T3` und Ordner wie in E3:
|
||||||
|
```
|
||||||
|
T3=$(head -c 48 /dev/urandom | base64 -w0 | tr -d '+/=' | head -c 43); echo "$T3"; docker exec webklar-workers sh -c "mkdir -p /data/vorschau/$T3 && echo '<h1>Test</h1>' > /data/vorschau/$T3/index.html"
|
||||||
|
```
|
||||||
|
Dann das Angebot mit einer Adresse, die nicht in `ABO_TEST_EMAILS` steht (und keinem Kunden gehört):
|
||||||
|
```
|
||||||
|
docker exec -w /app webklar-kundenbereich-server timeout 60 node --max-old-space-size=256 scripts/workers-kauf-testangebot.mjs --email kenso.gri+voll@gmail.com --firma "Testbetrieb GmbH" --vorschau "$T3" --voller-preis
|
||||||
|
```
|
||||||
|
Öffne den Link, bestelle mit Start „sofort“ und brich bei Stripe ab. *Erwartet:* 20,00 € pro Monat ohne Rabattzeile (das Skript meldet beim Anlegen „Achtung: … steht nicht in ABO_TEST_EMAILS …“). Räum danach auf: den Stripe-Kunden dieser Adresse im Dashboard löschen (nach dem Löschen des Angebots findet das Ablauf-Ereignis nichts mehr, der Kunde bliebe bis zum Tagesjob nach 30 Tagen stehen), das Angebot mit `--loeschen` und seiner Kennung aus der Zeile `Angebot:` wie in E13 (3), dann den Ordner:
|
||||||
|
```
|
||||||
|
[ "${#T3}" = 43 ] && docker exec webklar-workers rm -r -- "/data/vorschau/$T3"
|
||||||
|
```
|
||||||
|
*Wenn nicht:* ein Rabatt oder 0,50 € = Schalter bleibt aus, Claude Bescheid (der Rabatt dürfte hier nicht erscheinen).
|
||||||
|
|
||||||
|
**E11. [Claude] Repo und Nebenwirkungen prüfen.** Sag Claude: „Repo WEBklar/testbetrieb-gmbh prüfen.“ Claude liest per Gitea-Werkzeug nur lesend. *Erwartet:* der Dateibaum enthält genau `index.html` und `.webklar-preview.json`, der neueste Commit heißt „Erste Version der Website“ (darunter „Update preview config from ticket system“ und „Initial commit“); in WOMS erscheint dazu kein Auto-Ticket „Website …“, kein Arbeitsblatt und keine Push-Zeitmail, und es ging keine Mail T17 („Repo zuordnen“) raus. Die alte Vorschau-Adresse ist tot: `curl -s -o /dev/null -w '%{http_code}\n' https://entwurf.project.webklar.com/$T/` zeigt `404`. *Wenn nicht:* weitere Dateien im Repo oder ein anderer Commit-Titel = die Annahmen zu Gitea stimmen nicht; T25 „Vorschau konnte nicht übernommen werden“ mit Code `zu_viele_loeschungen`, `marker_fehlt`, `baum_abgeschnitten` oder `fremde_organisation` = ebenfalls Claude.
|
||||||
|
|
||||||
|
**E12. Lauf 2** (Kundenart **privat**, Start **sofort**; zahlt 0,50 € wirklich). Neuer Token und neues Testangebot, dann wie E6 bis E10 mit Alias 2:
|
||||||
|
```
|
||||||
|
T2=$(head -c 48 /dev/urandom | base64 -w0 | tr -d '+/=' | head -c 43); echo "$T2"; docker exec webklar-workers sh -c "mkdir -p /data/vorschau/$T2 && echo '<h1>Test</h1>' > /data/vorschau/$T2/index.html"
|
||||||
|
```
|
||||||
|
```
|
||||||
|
docker exec -w /app webklar-kundenbereich-server timeout 60 node --max-old-space-size=256 scripts/workers-kauf-testangebot.mjs --email kenso.gri+kauftest2@gmail.com --firma "Testbetrieb GmbH" --vorschau "$T2"
|
||||||
|
```
|
||||||
|
Schreib die Zeile `Angebot:` auf. **Fehlerprobe zuerst** (sie probt H3 und H4 gegen echtes Stripe, Gitea und Appwrite, bevor es ernst wird): Benenne den Ordner um, dann kann der Kauf die Vorschau nicht lesen.
|
||||||
|
```
|
||||||
|
docker exec webklar-workers mv /data/vorschau/$T2 /data/vorschau/$T2.weg
|
||||||
|
```
|
||||||
|
Bestelle als Privatperson (ein Name wie „Max Kauftest“) und wähle „sofort“. *Erwartet:* das Betriebsfeld ist ausgeblendet; Danke-Seite, K01 und K44 kommen (K44 ohne Projektadresse, mit dem Absatz zum Sofortbeginn statt des Datums nach der Widerrufsfrist), im Team-Postfach die Mail `[Hustler] Vorschau konnte nicht übernommen werden` (hier Absicht), das Angebot steht auf `bezahlt`, ein Projekt gibt es noch nicht. Dann stellst du den Ordner zurück:
|
||||||
|
```
|
||||||
|
docker exec webklar-workers mv /data/vorschau/$T2.weg /data/vorschau/$T2
|
||||||
|
```
|
||||||
|
Wähle im Stripe-Dashboard bei `checkout.session.completed` dieser Sitzung „Erneut senden“ (H4). *Erwartet:* Zustellung `200`; das Projekt entsteht und heißt wie der Käufer („Max Kauftest“, Repo nach dem Muster `WEBklar/max-kauftest`), nicht „Testbetrieb GmbH“; keine zweite K01 oder K44 und kein neuer Hinweis. Danach laufen die Prüfungen E7 bis E11 für Lauf 2 (das Projekt gibt es erst jetzt; bei `checkout.session.completed` steht dein Erneut-Senden als zweite Zustellung in der Liste, das ist hier richtig): in `featureConsents` beginnt der `beginn`-Text mit „Sofort. Ich verlange ausdrücklich …“ und `kundenart` ist `privat`; in Stripe sind bei Sofortbeginn auch `invoice.paid` und `customer.subscription.created` mit `200` zugestellt (keine `500`, auch wenn `invoice.paid` vor `checkout.session.completed` ankommt). *Wenn nicht:* kommt nach dem Erneut-Senden kein Projekt oder derselbe Hinweis noch einmal, ist die Wiederholung (H4) nicht wirksam: Claude Bescheid, die Schalter bleiben aus; ein Ordner `$T2.weg`, der stehen bleibt, wird von E13 (5) nicht entfernt (`docker exec webklar-workers rm -r -- "/data/vorschau/$T2.weg"`).
|
||||||
|
|
||||||
|
**E13. Aufräumen** (in dieser Reihenfolge; das Skript sagt am Ende selbst, was von Hand bleibt).
|
||||||
|
(1) Stripe: die beiden Abos sofort beenden, danach die Stripe-Kunden löschen (Lauf 2 hat 0,50 € bezahlt: erstatten, wenn du magst; ohne Beenden würde Lauf 1 nach der Widerrufsfrist 0,50 € abbuchen).
|
||||||
|
(2) WOMS: die beiden Testkunden löschen (das entfernt Portal-Zugang und Appwrite-Benutzer), die Projekte archivieren, die Repos `WEBklar/testbetrieb-gmbh` und `WEBklar/max-kauftest` archivieren oder löschen.
|
||||||
|
(3) Pro Angebot, mit den Kennungen aus E5 und E12 (`t-XXXXXXXX` ersetzen):
|
||||||
|
```
|
||||||
|
docker exec -w /app webklar-kundenbereich-server timeout 60 node --max-old-space-size=256 scripts/workers-kauf-testangebot.mjs --loeschen t-XXXXXXXX
|
||||||
|
```
|
||||||
|
*Erwartet:* `gelöscht: …`-Zeilen für Angebot, Vorschau, die Ereignisse `deal` und `fehler` (soweit vorhanden) und Interessent, Exit 0.
|
||||||
|
(4) Von Hand in der Appwrite-Konsole nach der `customerId` der Testkunden filtern und löschen: `featureConsents`, `contractEvents`, `subscriptions`, `websiteProjects`.
|
||||||
|
(5) Die Ordner im Dienst (das nächtliche Aufräumen des Dienstes entfernt diese Ordner nie):
|
||||||
|
```
|
||||||
|
[ "${#T}" = 43 ] && [ "${#T2}" = 43 ] && docker exec webklar-workers rm -r -- "/data/vorschau/$T" "/data/vorschau/$T2"
|
||||||
|
```
|
||||||
|
(6) `.env`: die Testadressen entfernen, den Schalter wieder auf `off`, neu starten (die Rechtsrunde läuft ohne eingeschaltete Seite):
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && sed -i 's/,\{0,1\}kenso\.gri+kauftest[12]@gmail\.com//g; s/^WORKERS_KAUF_ENABLED=.*/WORKERS_KAUF_ENABLED=off/' .env && grep -c 'kauftest' .env; grep -c '^WORKERS_KAUF_ENABLED=off$' .env
|
||||||
|
```
|
||||||
|
*Erwartet:* `0`, dann `1`. Dann `cd /home/webklar/apps && docker compose up -d --no-deps kundenbereich-server` und nach 1 bis 2 Minuten `docker exec webklar-kundenbereich-server printenv WORKERS_KAUF_ENABLED` (`off`). *Wenn nicht:* bei `[ "${#T}" = 43 ]` ohne Ausgabe ist eine der Shell-Variablen leer: dann nichts löschen, die Ordnernamen von Hand eintippen.
|
||||||
|
|
||||||
|
### F. Rechtsrunde (nach der Testbestellung, vor dem Einschalten)
|
||||||
|
|
||||||
|
Du gibst die neuen Käufer-Texte mit der echten Seite aus E vor Augen frei. Ohne deine Freigabe bleibt `WORKERS_KAUF_ENABLED=off` und `WORKERS_KAUFLINK=nein`.
|
||||||
|
|
||||||
|
**F1. [Claude] Wortlaute zusammenstellen.** Sag Claude: „Alle neuen Käufer-Texte des Kaufwegs für die Rechtsrunde.“ Claude liest sie aus dem Code und legt sie dir im Wortlaut vor (Seite: `server/services/workersKaufTexte.js` und `public/uebernahme*.js`; Stripe-Hinweis: `checkoutMessage` in `server/services/stripeBilling.js`; Mail: `server/mail-templates/K44 Website übernommen.html`; Fehlersätze: `server/routes/uebernahme.js` und `server/services/workersKauf.js`). Du gibst frei oder korrigierst:
|
||||||
|
- die Frage zur Kundenart und beide Antworten („Bestellen Sie als Unternehmen oder privat?“, „Ich bestelle als Unternehmen oder Selbstständige(r).“, „Ich bestelle als Privatperson.“);
|
||||||
|
- den Stripe-Hinweis über dem Zahlen-Knopf („Ihre Bestellung haben Sie auf der Seite „Website übernehmen“ abgegeben …“);
|
||||||
|
- den Angebotssatz auf der Seite und im Zustimmungsnachweis (F2);
|
||||||
|
- die Sätze zum Leistungsbeginn (Seite, Tabelle „Fällig“ und „Leistungsbeginn“, K44) für „sofort“ und „nach Ablauf der Widerrufsfrist“ und den Hinweis an Firmenkunden („Firmenkunden haben kein Widerrufsrecht und wählen am besten „Sofort“.“);
|
||||||
|
- die Leistungsbeschreibung vor „Ihre Bestellung“ (dieselbe Liste wie die Bestellseite im Portal; dort steht außerdem ein Hinweis auf die Übernahmegutschrift, hier nicht: bewusst?);
|
||||||
|
- die Danke-Seite (sie verspricht zwei E-Mails; bei einem Konflikt der Adresse nach der Zahlung gibt es weder K01 noch K44), den Satz „Zu dieser E-Mail-Adresse gibt es schon ein Kundenkonto …“ und die Seite für einen toten Link („Dieser Link ist nicht mehr gültig“ mit „Bitte melden Sie sich bei uns. Wir helfen Ihnen gern weiter.“ und der Kontaktzeile; der Käufer erfährt nie den Grund);
|
||||||
|
- dass der Link-Token im Verlauf und in den Lesezeichen des Käufers steht (bewusst: Neuladen und „Zurück“ von Stripe brauchen ihn).
|
||||||
|
|
||||||
|
**F2. Der Angebotssatz stimmt überall überein.** Vergleiche wortgleich: den Satz der Testbestellung (Ausgabe in E4, Seite, `featureConsents.texte`, erster Eintrag) mit `workersSettings/main.offerText` (WOMS → Hustler → Hochhaus → Einstellungen). Er muss denselben Preis nennen wie der Katalog: 20 € im Monat, 12 Monate Mindestlaufzeit, ohne MwSt. nach § 19 UStG; der Kaufweg verkauft nur den Katalogpreis. Neue Angebote übernehmen den Satz der Einstellungen zum Zeitpunkt des Anlegens (Schnappschuss, bestehende behalten ihren); jede spätere Änderung von `offerText` braucht darum eine neue Freigabe. *Erwartet:* alle Stellen wortgleich. *Wenn nicht:* Satz in den Einstellungen korrigieren und E4 mit einem neuen Testangebot wiederholen.
|
||||||
|
|
||||||
|
**F3. Datenschutzerklärung.** Prüfe in `public/datenschutz.html` und in der PDF-Fassung, dass dieser Ablauf gedeckt ist: Bestellung ohne Anmeldung mit Name, Anschrift, E-Mail (bei Unternehmen auch Betriebsname); der Stripe-Kunde entsteht schon beim Klick auf „Zahlungspflichtig bestellen“, also vor der Zahlung; IP-Adresse und Browser stehen im Zustimmungsnachweis; **Löschfrist:** der Stripe-Kunde einer nicht abgeschlossenen Bestellung wird gelöscht, wenn die Sitzung abläuft (bei Stripe standardmäßig nach 24 Stunden), spätestens nach 30 Tagen vom Tagesjob; die Checkout-Sitzungsobjekte selbst bleiben bei Stripe bestehen. Der Abschnitt „Interessenten“ ist bisher der einzige Text zu Interessenten. Fehlt ein Satz, schreibt Claude ihn, du gibst ihn frei, Claude ergänzt Seite und PDF (eigener Push wie C2) und lädt die neue Fassung in WOMS → Rechtliches hoch.
|
||||||
|
|
||||||
|
**F4. Entscheidung.** Freigabe = weiter mit G. Änderungswünsche: Claude ändert, Push wie C2, der Schalter bleibt aus; änderst du Texte der Seite oder der Mails, siehst du sie in einer kurzen Wiederholung von E1 bis E8 (neues Testangebot) noch einmal an und räumst wieder auf (E13).
|
||||||
|
|
||||||
|
### G. Einschalten
|
||||||
|
|
||||||
|
**G1. Voraussetzungen** (alle müssen stimmen): A4 (Abo freigegeben), C3 bis C7 grün, D1 und D3 erledigt, E komplett aufgeräumt (E13), F freigegeben. Der Kaufweg steht nach G2 für jeden mit gültigem Link offen.
|
||||||
|
|
||||||
|
**G2. Schalter an und Neustart.**
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && sed -i 's/^WORKERS_KAUF_ENABLED=.*/WORKERS_KAUF_ENABLED=on/' .env && grep -c '^WORKERS_KAUF_ENABLED=on$' .env
|
||||||
|
```
|
||||||
|
*Erwartet:* `1`. Dann:
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && docker compose up -d --no-deps kundenbereich-server
|
||||||
|
```
|
||||||
|
Nach 1 bis 2 Minuten `docker exec webklar-kundenbereich-server printenv WORKERS_KAUF_ENABLED` (*Erwartet:* `on`) und die Prüfung C3 noch einmal: *Erwartet:* dieselbe neutrale 404-Antwort mit Body und Kopfzeilen (ein unbekannter Link verrät nichts).
|
||||||
|
|
||||||
|
**G3. Kauf-Link im Dienst einschalten** (erst jetzt; im Hochhaus darf gerade kein Bau laufen, Bob, Bianca, Ben und Paul stehen nicht auf „arbeitet“, denn ein Neustart verwirft einen laufenden Bau, siehe P4 I3).
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && cp -p .env .env.bak-$(date +%F)-workers-p5b && if grep -q '^WORKERS_KAUFLINK=' .env; then sed -i 's/^WORKERS_KAUFLINK=.*/WORKERS_KAUFLINK=ja/' .env; else printf '\nWORKERS_KAUFLINK=ja\n' >> .env; fi; grep -c '^WORKERS_KAUFLINK=ja$' .env
|
||||||
|
```
|
||||||
|
*Erwartet:* `1`. Dann:
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && docker compose up -d --no-deps workers
|
||||||
|
```
|
||||||
|
Danach `docker exec webklar-workers printenv WORKERS_KAUFLINK` (*Erwartet:* `ja`). Ab jetzt legt Theo bei einem Kaufwunsch nach einer gesendeten Vorschau einen Kauf-Link an und schickt ihn mit dem Angebotssatz. *Wenn nicht:* `nein` = Zeile in `.env` prüfen; zurück: I1.
|
||||||
|
|
||||||
|
**G4. Erster echter Link.** Sobald der erste Kaufwunsch kam: Appwrite-Konsole → `workersOffers`, das neue Angebot: `offerText` gesetzt (derselbe Satz wie in der Nachricht), `status` = `offen`, `previewUrl` beginnt mit `https://entwurf.project.webklar.com/`, `expiresAt` in rund 30 Tagen. Öffne den Link aus der Nachricht in einem privaten Fenster (Seite zeigt das Angebot; ein Link ohne `#t=…` zeigt „nicht mehr gültig“), auch aus Gmail, Outlook (Safe Links) und iOS Mail: der Teil nach `#t=` muss erhalten bleiben. *Wenn nicht:* `offerText` fehlt = A2 und A3; `previewUrl` mit anderer Adresse = die Vorschau-Adresse von Dienst und Portal stimmen nicht überein (Claude). Der erste echte Kauf liest zum ersten Mal eine vom Dienst gebaute Vorschau (mehrere Dateien, `.css`/`.svg`, Rechte vom Dienst), nicht den Testordner. Kommt danach der Hinweis `[Hustler] Vorschau konnte nicht übernommen werden`, ist die Ursache in der Mail genannt (H3); sonst prüfst du im neuen Projekt, dass alle Dateien der Vorschau im Repo liegen.
|
||||||
|
|
||||||
|
**G5. Nach dem ersten echten Kauf.** *Erwartet:* kein Hinweis „[Hustler] …“; im Hustler ist der Interessent `deal`, und die Kennzahlen für neue Abos und Deals im Monat steigen um 1 (neuer Monatsumsatz: 20 €); der Dienst schickt dem Interessenten keine weitere Mail. Achte auf den Projektnamen: bestellt ein Käufer als privat, heißt das Projekt nach ihm, nicht nach dem Betrieb. „Bezahlt“ heißt „Abo abgeschlossen“, nicht „Geld da“: schlägt eine SEPA-Lastschrift später fehl, gilt der normale Abo-Weg (Mail T04, `invoice.payment_failed`). Am nächsten Morgen (nach 03:20) prüfst du, dass das `workersPreviews`-Dokument des Kaufs seinen Status behalten hat (`convertedProjectId` gesetzt), der Ordner im Dienst noch steht und das Angebot `bezahlt` bleibt; das Aufräumen darf übernommene Vorschauen nie anfassen (ihr Löschtermin `deleteAfter` liegt bei der Voreinstellung 60 Tage nach dem Bau, die Prüfung zeigt am nächsten Morgen also nur, dass nichts schiefging).
|
||||||
|
|
||||||
|
### H. Betrieb und Handfälle
|
||||||
|
|
||||||
|
**H1. Was der Käufer als Fehler sieht.** Kein Befehl. „Dieser Link ist nicht mehr gültig.“ (404) kommt bei jedem Grund gleich: Angebot abgelaufen, schon bezahlt, Interessent gesperrt/verloren, Vorschau übernommen oder verworfen, Schalter aus; den Grund liest du im `workersOffers`-Dokument. „Zu dieser E-Mail-Adresse gibt es schon ein Kundenkonto …“ (409) = die Adresse gehört einem Kunden oder Mitarbeiter: kein Kauf, das Team klärt es von Hand. „Zu viele Anfragen. Bitte später erneut versuchen.“ (429) = höchstens 10 Bestellversuche je Stunde und Adresse (und je Link), 120 Seitenaufrufe je Stunde und Adresse: eine Stunde warten. „Die Online-Bestellung ist noch nicht freigeschaltet.“ (503) = Stripe im Portal nicht eingerichtet. „Das hat gerade nicht geklappt. Bitte versuchen Sie es in einigen Minuten noch einmal.“ (500): `docker logs --since 1h webklar-kundenbereich-server 2>&1 | grep '\[uebernahme\]'` zeigt nur Art, Status und Code, nie Token oder Adressen. Die Seite nimmt nur JSON bis 16 KB (größer: 413).
|
||||||
|
|
||||||
|
**H2. Täglich: nichts zu tun.** Der Tagesjob des Portals (Stripe-Kunden abgebrochener Bestellungen, D3) und das nächtliche Aufräumen des Dienstes um 03:20 (hängende Kaufversuche nach Ablauf auf „abgelaufen“, Vorschauen übernommener Käufe bleiben stehen; P4 I4) laufen von selbst. Im Log steht nur dann etwas, wenn etwas gelöscht wurde oder schiefging:
|
||||||
|
```
|
||||||
|
docker logs --since 24h webklar-kundenbereich-server 2>&1 | grep -E '\[workers-kauf\]|\[uebernahme\]'
|
||||||
|
```
|
||||||
|
*Erwartet:* keine Zeile oder `Aufräumen Stripe-Kunden: …` mit Zahlen. *Wenn nicht:* Zeilen `[workers-kauf] … status=… code=…` an Claude; sie enthalten nie Adressen oder Token.
|
||||||
|
|
||||||
|
**H3. Hinweise `[Hustler] …` (T25).** Ein Hinweis des Kaufwegs kommt als Mail an das Team (Betreff `[Hustler] <Titel>`) und steht als Ereignis „fehler“ von Theo im Hustler. Jeder Titel heißt: der Käufer hat gezahlt und etwas ist nicht ganz durchgelaufen. Die Mail nennt Angebot, Stripe-Sitzung (`cs_…`) und Abo (`sub_…`). Der Käufer hat in manchen Fällen noch kein K01/K44: seine Vertragsbestätigung (§ 312f BGB) muss am selben Tag raus, also zuerst die Ursache klären. „Ereignis erneut senden“ ist in H4 erklärt.
|
||||||
|
- **E-Mail-Adresse gehört schon einem Kunden:** kein Konto angelegt, kein Passwort, weder K01 noch K44. Prüfe, ob es dieselbe Person ist und ob der Bestandskunde schon ein laufendes Abo hat: hat er eins, erstattest du und beendest das neue Abo (sonst entstünde ein zweites). Hat er keins: in Stripe am Abo **und** am Stripe-Kunden `webklarCustomerId` = Kunden-Id aus WOMS setzen, dann das Ereignis erneut senden; K01 geht raus, K44 nur, wenn der Kunde noch ein gespeichertes Anfangspasswort hat (an die Adresse im Kundendokument, nicht an die der Bestellung). Gehört die Adresse nur einem Appwrite-Benutzer ohne Kunden: erstatten (den Benutzer löschst du erst, wenn du sicher bist, dass er niemandem gehört).
|
||||||
|
- **Doppelte Zahlung für ein Angebot:** die zweite Zahlung erstatten und das zweite Abo beenden; es entstand kein zweiter Kunde.
|
||||||
|
- **Zahlung ohne Angebot:** das Angebot gibt es nicht mehr (zum Beispiel mit `--loeschen` entfernt); nichts wurde angelegt: erstatten oder den Kunden von Hand anlegen.
|
||||||
|
- **Bezahlte Sitzung ohne Abo:** Zahlung bei Stripe prüfen; es wurde nichts angelegt.
|
||||||
|
- **Kunde zum Angebot fehlt:** Angebot oder Abo verweist auf einen Kunden, den es nicht gibt: Kunden von Hand prüfen; ist die Ursache behoben, Ereignis erneut senden, sonst erstatten.
|
||||||
|
- **Bezahlt, Konto fehlt:** das Konto ist nicht vollständig entstanden (Schritt und Fehler stehen in der Mail, oft ein fehlendes Feld in `customers`); Ursache beheben, Ereignis erneut senden, sonst erstatten.
|
||||||
|
- **Zustimmungsnachweis nicht gespeichert:** der Kauf ist abgeschlossen; die Zustimmung steht in den Stripe-Metadaten des Abos (`termsVersion`, `termsAcceptedAt`, `consentIp`, `consentUserAgent`, `workersAngebotssatz`). Ereignis erneut senden holt den Nachweis nach, oder du legst ihn in `featureConsents` von Hand an.
|
||||||
|
- **Vorschau konnte nicht übernommen werden:** der Kauf lief ohne Vorschau weiter (K44 ging ohne Projektadresse raus). Die Mail nennt die Ursache: Im Ordner der Vorschau im Dienst (`/data/vorschau/<Token>`) sind höchstens 6 flache Dateien `.html`/`.css`/`.svg` erlaubt, zusammen höchstens 200 KB, mit `index.html`, ohne Unterordner; fehlt der Ordner, holt auch ein erneut gesendetes Ereignis nichts nach: stelle ihn aus einer Sicherung wieder her oder lege die Website von Hand ins Repo des Kunden. Steht der Ordner wieder da, Ereignis erneut senden: es holt Projekt, Dateien und Nachweis nach, ohne Doppeltes und ohne neue Mail an den Käufer; die Adresse des Projekts gibst du ihm von Hand weiter. Der Hinweis kann sich bei jedem Versuch wiederholen, solange die Ursache besteht.
|
||||||
|
- **Zugangsdaten nicht zustellbar:** der Mailserver hat die Adresse im Kundendokument abgelehnt (vertippt?). Korrigiere sie im WOMS in der Kundenakte; K44 geht bei der nächsten Wiederholung von Stripe (bis zu drei Tage) oder per „Ereignis erneut senden“ an die Adresse im Kundendokument. Das Anfangspasswort bleibt bis dahin gespeichert.
|
||||||
|
- **Zugangsdaten nicht zugestellt:** das Kundendokument war nicht lesbar, K44 ging nicht raus: Zugangsdaten von Hand schicken (Kundenakte im WOMS), Kundendokument prüfen.
|
||||||
|
- **Passwort im Kundendokument nicht gelöscht:** K44 ist angekommen, aber das Feld `portalPassword` steht noch: in der Kundenakte von Hand leeren (sonst schickt ein erneut gesendetes Ereignis K44 noch einmal).
|
||||||
|
- **Rückmeldung an den Hustler nicht gespeichert:** Kunde und Abo stehen; im Hustler fehlt „deal“ oder „bezahlt“. Ursache beheben (Schritt steht in der Mail), Ereignis erneut senden.
|
||||||
|
|
||||||
|
**H4. „Ereignis erneut senden“** (Stripe-Dashboard → Entwickler → Ereignisse): das Ereignis `checkout.session.completed` der Sitzung aus dem Hinweis öffnen und beim Endpunkt `https://project.webklar.com/api/abo/webhook` „Erneut senden“ wählen (Stripe hält Ereignisse 30 Tage vor). Der Handler prüft zuerst, ob alles schon fertig ist (Projekt, Nachweis, erster Commit); ist es fertig, liest er nur und schreibt und mailt nichts. Sonst holt er nach, was fehlt, ohne Doppeltes und ohne neue Mail an den Käufer. *Erwartet:* Zustellung `200`, der Hinweis kommt nicht wieder. *Wenn nicht:* kommt derselbe Hinweis noch einmal, besteht die Ursache weiter.
|
||||||
|
|
||||||
|
**H5. Waisen-Repos.** Bricht ein Lauf zwischen dem Anlegen des Repos und dem ersten Projektdokument ab, bleibt ein Repo ohne Projekt, und die Wiederholung nimmt den Namen mit `-2` (`<name>` neben `<name>-2`). Schau nach dem Test und nach den ersten echten Käufen in der Gitea-Organisation WEBklar (oder in WOMS unter „Art fehlt“/unzugeordnet) nach solchen Paaren und lösche das Repo ohne Projekt von Hand.
|
||||||
|
|
||||||
|
### I. Rückweg
|
||||||
|
|
||||||
|
**I1. Kauf-Link zuerst aus** (kein Bau darf laufen, siehe G3).
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && cp -p .env .env.bak-$(date +%F)-workers-p5c && sed -i 's/^WORKERS_KAUFLINK=.*/WORKERS_KAUFLINK=nein/' .env && grep -c '^WORKERS_KAUFLINK=nein$' .env
|
||||||
|
```
|
||||||
|
*Erwartet:* `1`. Dann `cd /home/webklar/apps && docker compose up -d --no-deps workers` und `docker exec webklar-workers printenv WORKERS_KAUFLINK` (`nein`). Danach legt Theo keine Kauf-Links mehr an, Kaufwünsche landen wieder als Pult-Eintrag „Kaufwunsch – Kauf-Link noch nicht aktiv“.
|
||||||
|
|
||||||
|
**I2. Dann die Seite aus.**
|
||||||
|
```
|
||||||
|
cd /home/webklar/apps && sed -i 's/^WORKERS_KAUF_ENABLED=.*/WORKERS_KAUF_ENABLED=off/' .env && grep -c '^WORKERS_KAUF_ENABLED=off$' .env
|
||||||
|
```
|
||||||
|
*Erwartet:* `1`. Dann `cd /home/webklar/apps && docker compose up -d --no-deps kundenbereich-server` und nach 1 bis 2 Minuten die Prüfung C3. Schon verschickte Links zeigen jetzt „Dieser Link ist nicht mehr gültig.“. Der Webhook-Zweig bleibt wirksam: schon bezahlte Sitzungen werden weiter verarbeitet, und der Tagesjob läuft weiter.
|
||||||
|
|
||||||
|
**I3. Den Code nie zurückrollen**, solange `workersOffers` im Status `checkout` stehen oder in den letzten 3 Tagen jemand bezahlt hat (Appwrite-Konsole, Sammlung `workersOffers`, nach `status` und `paidAt` sortieren): die alte Fassung kennt `workersOfferId` nicht, und bezahlte Sitzungen liefen ohne Kunden durch den normalen Abo-Weg. Für den Rückweg genügen die beiden Schalter. Was bleibt: angelegte Kunden, Projekte, Nachweise und Stripe-Abos; die Vorschau-Ordner im Volume; die `.env`-Zeilen.
|
||||||
|
|
||||||
|
### J. Übergabe-Hinweise
|
||||||
|
|
||||||
|
**J1. Umschalten auf Fassung 6 (Kundenansicht P10/P11).** Der Kaufweg zeigt die eingefrorene Fassung `2026-09-30-v5`. Stellt jemand `config.abo.termsVersion` auf v6, bevor die Kaufweg-Texte umgestellt sind, nennt der Nachweis v6 für einen Wortlaut v5. Darum zuerst I1 und I2 (Link und Seite aus), dann `server/services/workersKaufTexte.js` und die Fixture `test/support/wortlautAboV5.json` auf den Wortlaut v6 umstellen (der Test `test/workersKaufTexte.test.js` meldet bis dahin „Kaufweg auf Fassung 6 umstellen (Kundenansicht P10)“), neue Testbestellung (E) und Rechtsrunde (F), erst dann wieder G. Dieser Punkt gehört in die Checkliste der Fassung-6-Umschaltung (Kundenansicht, Teil C „Fassung 6 festschreiben“); Claude trägt ihn dort schon vor dem Push in C1 ein, zusammen mit dem Vermerk, dass K44 und T25 belegt sind.
|
||||||
|
|
||||||
|
**J2. Nummern.** Mail K44 („Website übernommen“) und Team-Hinweis T25 („Workers Hinweis“) sind durch P5 belegt; die nächsten freien Nummern sind K45 und T26.
|
||||||
|
|
||||||
|
**J3. Übernommene Vorschau trägt `noindex`.** Jede Vorschau-Seite enthält ein `noindex`-Meta (die Prüfung des Dienstes verlangt es). Bevor die Kundendomain eines übernommenen Projekts live geht, muss es raus, sonst taucht die Seite nie in Suchmaschinen auf. Sag dem Team Bescheid.
|
||||||
|
|
||||||
|
**J4. Projektnamen.** Ein Unternehmen heißt im Projekt wie sein Betrieb, eine Privatperson wie sie selbst; gibt es den Namen schon, hängt das System `-2` an (H5).
|
||||||
|
|
||||||
|
**Fertig, wenn:** B5, C3 bis C7, D1 und D3, E4 bis E12 (Lauf 1, Fehlerprobe und Lauf 2) ihre „Erwartet“-Zeilen erfüllen, E13 aufgeräumt ist und F freigegeben hat. Das Einschalten (G) ist eine eigene Entscheidung.
|
||||||
Reference in new issue
Block a user