Stripe-Integration (S4, S5, S7.6): Abos, Zahlungsart ändern, Zahlarten-Kommunikation
Phase S4 – Abos über Stripe (E2E komplett im Testmodus): - Abo-/Recovery-Checkout: customer_creation=always + setup_future_usage=off_session - StripePaymentService::chargeAbo (off-session PaymentIntent, Idempotency-Key), StripeGateway, Provider-Dispatch im Abbuchungs-Cron (PAYONE-Pfad unverändert) - Webhook: Zahlungsmittel-Capture vor Paid-Pfad, 503 payment_not_yet_visible bei Cron-Transaktions-Race; Command stripe:process-pending-events (Cron-Ende + 10-min Scheduler) verarbeitet liegengebliebene Events nach - createNewAbo/advanceAboAfterRecoveryPayment Stripe-aware inkl. Race-Absicherung; Recovery-Orders nutzen advanceAboAfterRecoveryPayment (latenter PAYONE-Duplikat-Bug) - Migrationen: Stripe-Abo-Spalten auf shopping_payments; clearingtype varchar(3)→(20) inkl. Reparatur der zu 'str' gekürzten Live-Zeilen Phase S5 – Zahlungsart ändern (Scope: jede Zahlart, nicht nur Kreditkarte): - Stripe Checkout mode=setup ohne Abbuchung; PAYONE-Abos ziehen beim Wechsel auf Stripe um (Auslaufmodell §7.2) - AboPaymentMethodService: beginStripeChange/finalize/applyStripePaymentMethod (idempotent, detacht alte PM, card_changes-Log); Webhook setup_intent.succeeded als Sicherheitsnetz; Einstiege Portal/CRM/Berater-Kunden-Link flow-abhängig - Views/Mails flow-abhängig („Zahlungsweise" statt „Kreditkarte") Phase S7.6 – Zahlarten-Kommunikation: - Einheitliche Zahlarten-Logos (SVG) + wiederverwendbares Partial payment_logos; Checkout/Footer zeigen Kern-Zahlarten + „···"-Icon, /zahlungsarten komplett neu mit allen aktiven Stripe-Zahlarten (gegen Account-Konfiguration verifiziert) und Vorkasse (PAYONE); Sofort/SEPA entfernt Tests: StripeAboTest (15), StripePaymentMethodChangeTest (10), PaymentMethodsCommunicationTest (28), PaymentProviderManagerTest angepasst. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e1da90ff91
commit
dd1c8f9a1e
68 changed files with 2856 additions and 236 deletions
|
|
@ -1,6 +1,21 @@
|
|||
# Entwicklungsplan: Stripe als Zahlungsanbieter (PAYONE bleibt als Option)
|
||||
|
||||
> **Status:** In Arbeit (S0 ✓, S1 ✓, S2 ✓, S3 ✓ inkl. Browser-E2E — **Notfall-Go-Live wegen PAYONE-Ausfall, Abos eingefroren, s. §7.1**; als Nächstes S4) · **Erstellt:** 2026-07-08 · **Zuletzt aktualisiert:** 2026-07-08
|
||||
> **Status:** In Arbeit (S0 ✓, S1 ✓, S2 ✓, S3 ✓ — **live in Produktion**; S4 ✓ **E2E komplett** (Abschluss, Cron, Decline→Recovery, authentication_required); S5 ✓ „Zahlungsart ändern" inkl. E2E über CRM + Kunden-Link (nur Portal-Pfad ohne Sichttest, gleicher Code-Pfad); S7.6 ✓ Zahlarten-Kommunikation — **vor Abo-Freigabe:** Deployment + 3 Migrationen, dann `abo-payments-frozen` aufheben, §16) · **Erstellt:** 2026-07-08 · **Zuletzt aktualisiert:** 2026-07-09
|
||||
>
|
||||
> **Lage-Update 2026-07-09 (Stakeholder):** Das PAYONE-Konto ist **wieder freigeschaltet**.
|
||||
> Daraus ergeben sich folgende verbindliche Entscheidungen (Details §7.2):
|
||||
> 1. **Auslaufmodell statt Migration:** Bestehende PAYONE-Abos laufen unverändert über
|
||||
> PAYONE weiter (Kunden müssen nicht aktiv werden). **Neue** Abos werden über Stripe
|
||||
> angelegt; wird das **Zahlungsmittel eines Abos geändert** (Recovery-Zahlung → S4,
|
||||
> Kreditkarte-ändern-Flow → S5), wechselt das Abo dabei auf Stripe. PAYONE läuft so
|
||||
> Stück für Stück im Hintergrund aus.
|
||||
> 2. **Vorkasse (PAYONE) wieder anbieten:** Der Checkout zeigt neben der Stripe-Online-
|
||||
> Zahlung wieder „Vorkasse (PAYONE)" (Setting `payment-vorkasse-hidden` → aus).
|
||||
> 3. **Zahlarten-Kommunikation** (→ S7.6): Stripe-Zahlarten klar benennen, möglichst
|
||||
> mit Logos — im Checkout, auf den Shop-Seiten, im Footer (Zahlungsmöglichkeiten)
|
||||
> und auf der Seite `/zahlungsarten`. ✅ **umgesetzt 2026-07-09** (§11, Umsetzungsstatus S7.6).
|
||||
> 4. **Rückbuchungen & Stornierungen erfassen** — als eigener, **nachgelagerter** Punkt
|
||||
> aufgenommen (→ §12.1 / Phase S9).
|
||||
> **Test-Server:** Schalter `payment-active-provider` steht auf **stripe** (seit 2026-07-08, für den Manualtest); zurückstellen: `Setting::setContentBySlug('payment-active-provider', 'payone', 'text')`.
|
||||
> **Begleitdokumente:** `ANALYSE-IST-ZUSTAND.md` (Codebase-Analyse) · `STRIPE-KONZEPT.md` (Architektur & API-Mapping)
|
||||
> **Vorarbeiten:** `dev/2026-06-09-payments/ENTWICKLUNGSPLAN-ZAHLUNGSANBIETER.md` (Phase R ✅), `dev/2026-06-25-kreditkarte-update/`
|
||||
|
|
@ -284,7 +299,7 @@ unbehandelte Typen mit `200 ignored`).
|
|||
|
||||
---
|
||||
|
||||
## 7. Phase S3 – Einmalzahlung im Checkout ⭐ · Status: **UMGESETZT** (2026-07-08, Browser-E2E offen)
|
||||
## 7. Phase S3 – Einmalzahlung im Checkout ⭐ · Status: **ERLEDIGT & LIVE** (2026-07-09)
|
||||
|
||||
### Ziel
|
||||
Steht der Schalter auf `stripe`, laufen **neue Einmalzahlungen** (Shop, Zahlungslinks)
|
||||
|
|
@ -372,9 +387,9 @@ Payment-Regression (PAYONE + Abo-Retry/Recovery, 66 Tests) grün; Blade kompilie
|
|||
unabhängig von S3.)*
|
||||
|
||||
### Akzeptanzkriterien
|
||||
- [ ] **Browser-E2E (Manualtest, offen):** Checkout → Redirect zu Stripe → Zahlung (inkl. 3DS-Testkarte) → Rücksprung → Webhook → Order bezahlt → Rechnung aus dem System.
|
||||
- [ ] Apple Pay/Google Pay erscheinen auf fähigen Geräten ohne Zusatzcode; Zahlarten lassen sich im Stripe-Dashboard zu-/abschalten, ohne Deploy.
|
||||
- [ ] Abbruch bei Stripe führt sauber in den Checkout zurück (Manualtest).
|
||||
- [x] **Browser-E2E:** Checkout → Redirect zu Stripe → Zahlung → Rücksprung → Webhook → Order bezahlt → Rechnung aus dem System. *(2026-07-08 im Testmodus; seit Go-Live in Produktion bestätigt — Stakeholder 2026-07-09: „steht und funktioniert".)*
|
||||
- [x] Apple Pay/Google Pay erscheinen auf fähigen Geräten ohne Zusatzcode; Zahlarten lassen sich im Stripe-Dashboard zu-/abschalten, ohne Deploy.
|
||||
- [x] Abbruch bei Stripe führt sauber in den Checkout zurück.
|
||||
- [x] Umschalten des Schalters wechselt ausschließlich neue Checkouts (per Test nachgewiesen).
|
||||
|
||||
---
|
||||
|
|
@ -425,40 +440,61 @@ Alle Schalter sind damit ohne Tinker im Admin schaltbar (bestehender
|
|||
CC-Update-Freeze (`updateCreditCard` → Error mit Hinweistext), Manager-Defaults aus —
|
||||
in `StripeCheckoutTest.php`; Karten-Update-Regressionssuite (17 Tests) unverändert grün.
|
||||
|
||||
### Go-Live-Checkliste Produktion
|
||||
### Go-Live-Checkliste Produktion — ✅ durchgeführt am 2026-07-08 abends
|
||||
|
||||
1. [ ] Deployment des aktuellen Stands (Branch `payment/phase-r-payone-refactoring`).
|
||||
2. [ ] `php artisan migrate` (3 neue Migrationen: Provider-Spalten, `stripe_webhook_events`).
|
||||
3. [ ] Prod-`.env`: `STRIPE_KEY`/`STRIPE_SECRET` = **Live-Keys** (`pk_live_…`/`sk_live_…`), `STRIPE_MODE=live`.
|
||||
4. [ ] Stripe-Dashboard (**Live-Modus**): Zahlarten aktivieren; Webhook-Endpoint
|
||||
1. [x] Deployment des aktuellen Stands (Branch `payment/phase-r-payone-refactoring`).
|
||||
2. [x] `php artisan migrate` (damals 3 neue Migrationen: Provider-Spalten, `stripe_webhook_events`).
|
||||
3. [x] Prod-`.env`: `STRIPE_KEY`/`STRIPE_SECRET` = **Live-Keys** (`pk_live_…`/`sk_live_…`), `STRIPE_MODE=live`.
|
||||
4. [x] Stripe-Dashboard (**Live-Modus**): Zahlarten aktivieren; Webhook-Endpoint
|
||||
`https://api.mivita.care/stripe/webhook` mit der Event-Liste aus §6 anlegen;
|
||||
dessen Signing-Secret als `STRIPE_WEBHOOK_SECRET` in die Prod-`.env`.
|
||||
5. [ ] Caches: `php artisan config:clear && php artisan view:clear` (bzw. Deploy-üblich `config:cache`).
|
||||
6. [ ] Schalter setzen — im Admin unter **Einstellungen → „Zahlungen (Stripe / PAYONE)"**:
|
||||
*Stolpersteine (beide gelöst): falsches Webhook-Secret → alle Events 400;
|
||||
`charge.succeeded` war anfangs nicht abonniert → Zahlart-Anzeige blieb auf Fallback.*
|
||||
5. [x] Caches: `php artisan config:clear && php artisan view:clear` (bzw. Deploy-üblich `config:cache`).
|
||||
6. [x] Schalter setzen — im Admin unter **Einstellungen → „Zahlungen (Stripe / PAYONE)"**:
|
||||
☑ Checkout-Wartungsmodus (nur Admins bestellen), ☑ Abo-Zahlungen einfrieren,
|
||||
☑ Vorkasse ausblenden (**wichtig:** Vorkasse autorisiert über PAYONE und würde
|
||||
beim Ausfall crashen), Anbieter = **Stripe**.
|
||||
7. [ ] Smoke-Test **als Admin bei aktivem Wartungsmodus**: echte Bestellung (Live-Karte)
|
||||
→ Stripe-Redirect → bezahlt → Rechnung; ggf. im Stripe-Dashboard erstatten.
|
||||
Gegenprobe im Inkognito-Fenster: Besucher sehen die Wartungsmeldung.
|
||||
8. [ ] Checkout öffnen: **Wartungsmodus abhaken** — Einmalzahlungen laufen für alle über Stripe.
|
||||
9. [ ] Kontrolle: `storage/logs/stripe.log`, `stripe_webhook_events`, Payment-Dashboard.
|
||||
10. [ ] Nach S4: `abo-payments-frozen` zurücksetzen.
|
||||
7. [x] Smoke-Test **als Admin bei aktivem Wartungsmodus**: echte Bestellungen (u. a.
|
||||
57831, 57832 — müssen noch storniert/bereinigt werden, Statistik/Provision!).
|
||||
8. [x] Checkout geöffnet: Einmalzahlungen laufen seit 2026-07-08 abends für alle über Stripe.
|
||||
9. [x] Kontrolle: `storage/logs/stripe.log`, `stripe_webhook_events`, Payment-Dashboard.
|
||||
10. [ ] **Nach S4/S5 (JETZT offen, → §16):** Deployment + 3 neue Migrationen, dann
|
||||
`abo-payments-frozen` aufheben und Vorkasse wieder einblenden.
|
||||
|
||||
---
|
||||
|
||||
## 8. Phase S4 – Abos über Stripe
|
||||
## 7.2 PAYONE wieder freigeschaltet (2026-07-09): Auslaufmodell & Vorkasse
|
||||
|
||||
**Lage:** Das PAYONE-Konto ist wieder aktiv. Entscheidungen Stakeholder (2026-07-09):
|
||||
|
||||
| # | Entscheidung | Umsetzung |
|
||||
|---|--------------|-----------|
|
||||
| 1 | **Bestands-Abos bleiben auf PAYONE** — Kunden müssen nicht aktiv werden, die alte Verknüpfung läuft weiter. Keine aktive Migrationskampagne (S8 damit vorerst obsolet). | Bereits durch S1 abgedeckt (`providerFor($abo)` liest die Spalte). Nach S4-Abschluss: `abo-payments-frozen` → aus, der Cron bucht PAYONE-Abos wieder regulär ab. |
|
||||
| 2 | **Neue Abos → Stripe.** Abo-Erstzahlung im Checkout läuft über Stripe (Schalter steht auf `stripe`). | Phase S4 (dieser Sprint). |
|
||||
| 3 | **Zahlungsmittel-Änderung → Stripe.** Wo ein Abo-Zahlungsmittel neu hinterlegt wird — Recovery-Zahlung (S4) und Kreditkarte-ändern-Flow (S5) — wechselt das Abo dabei von PAYONE auf Stripe. So läuft PAYONE Stück für Stück im Hintergrund aus. | Recovery: S4. CC-Update: S5 (der Flow wird für PAYONE-Abos zum Stripe-SetupIntent-Flow → sanfte Migration; zieht den S8-Mechanismus faktisch vor). |
|
||||
| 4 | **Vorkasse (PAYONE) wieder anbieten.** Der Checkout zeigt neben „Online-Zahlung" (Stripe) wieder Vorkasse — klar als „Vorkasse (PAYONE)" benannt. | Ops: Setting `payment-vorkasse-hidden` im Admin abhaken. Code: Checkout-Label → `payment.prepayment_payone` („Vorkasse (PAYONE)"). |
|
||||
|
||||
**Freeze-Aufhebung (nach S4-Deployment):** `abo-payments-frozen` → aus. Danach:
|
||||
Bestands-PAYONE-Abos buchen wieder über PAYONE ab (Konto ist wieder frei), neue
|
||||
Abos entstehen mit `payment_provider='stripe'`, eingefrorene Abbuchungen werden über
|
||||
das unveränderte `next_date` automatisch nachgeholt.
|
||||
|
||||
---
|
||||
|
||||
## 8. Phase S4 – Abos über Stripe · Status: **ERLEDIGT** (2026-07-09, Testmodus-E2E komplett; offen nur Prod-Rollout/Freeze-Aufhebung → §16)
|
||||
|
||||
### Ziel
|
||||
Neue Abos: Erstzahlung im Checkout mit Speicherung des Zahlungsmittels; wiederkehrende
|
||||
Abbuchungen über den bestehenden Cron, off-session. Bestehende PAYONE-Abos laufen
|
||||
unverändert über PAYONE.
|
||||
unverändert über PAYONE (Auslaufmodell, §7.2). Recovery-Zahlungen laufen über Stripe
|
||||
und ziehen das Abo dabei auf Stripe um.
|
||||
|
||||
### Aufgaben
|
||||
|
||||
| # | Aufgabe |
|
||||
|---|---------|
|
||||
| S4.1 | Abo-Erstzahlung: Checkout Session (`mode=payment`) mit `setup_future_usage=off_session` + `customer`; Zahlarten auf **wiederverwendbare** Methoden eingeschränkt (Konzept §3.2; Klarna/Amazon Pay nur nach positiver N1-Prüfung); nach `succeeded` PaymentMethod-ID aus dem PaymentIntent ermitteln. |
|
||||
| S4.1 | Abo-Erstzahlung: Checkout Session (`mode=payment`) mit `setup_future_usage=off_session` + `customer_creation=always`; nach `succeeded` PaymentMethod-/Customer-ID aus dem PaymentIntent ermitteln. **N1 damit gelöst:** Stripe filtert bei gesetztem `setup_future_usage` die Hosted-Checkout-Zahlarten automatisch auf wiederverwendbare Methoden (Dynamic Payment Methods) — keine eigene Whitelist nötig, Klarna/Amazon Pay erscheinen nur, wenn Stripe sie off-session-fähig anbietet. Gilt auch für Recovery-Checkouts (`recovery_user_abo_id`): Zahlung on-session, Zahlungsmittel wird gespeichert, Abo wechselt auf Stripe (§7.2 Nr. 3). |
|
||||
| S4.2 | `AboHelper::createNewAbo()`: bei Stripe-Erstzahlung `payment_provider='stripe'`, `stripe_customer_id`, `stripe_payment_method_id` (statt `payone_userid`/`carddata`) übernehmen; maskierte Anzeige-Daten (Brand, letzte 4, Ablauf) als Anzeige-Metadaten speichern. |
|
||||
| S4.3 | `StripePaymentService::chargeAbo()`: off-session PaymentIntent (`customer`, `payment_method`, `off_session=true`, `confirm=true`), Idempotency-Key = `user_abo_order`-Referenz (Schutz gegen Doppel-Abbuchung bei Cron-Retry). |
|
||||
| S4.4 | `UserMakeOrder::makePayment()`: Provider-Dispatch über `PaymentProviderManager::providerFor($abo)` — PAYONE-Zweig byte-identisch. |
|
||||
|
|
@ -466,36 +502,184 @@ unverändert über PAYONE.
|
|||
| S4.6 | Abo-Ansichten (CRM `/user/abos`, Portal, Admin `AboController`): Provider + maskiertes Zahlungsmittel anzeigen; PAYONE-Felder bleiben für PAYONE-Abos maßgeblich. |
|
||||
| S4.7 | Abo-Retry (`AboRetryPaymentService`) provider-fähig (nutzt `chargeAbo` des Abo-Providers). |
|
||||
|
||||
### Tests
|
||||
- Erstzahlung → `UserAbo` mit Stripe-Feldern; PAYONE-Felder leer.
|
||||
- Cron-Abbuchung Stripe-Abo: Erfolg (Order paid, `user_abo_orders` fortgeschrieben), Decline (Status 3), Idempotenz (doppelter Cron-Lauf bucht nicht doppelt ab).
|
||||
- Gemischte Bestände: ein PAYONE-Abo + ein Stripe-Abo im selben Cron-Lauf → jeweils richtiger Gateway-Aufruf.
|
||||
### Vorab-Designentscheidungen (2026-07-09)
|
||||
|
||||
- **Transport der Stripe-IDs:** Neue Spalten `stripe_customer_id`, `stripe_payment_method_id`
|
||||
und `user_abo_id` auf `shopping_payments`. Der Webhook (`markPaid`) schreibt Customer/
|
||||
PaymentMethod an die Zahlung, bevor der Paid-Pfad läuft; `createNewAbo()`/
|
||||
`advanceAboAfterRecoveryPayment()` übernehmen sie von dort ans Abo.
|
||||
- **Race Browser-Return vs. Webhook:** Der Checkout-Erfolgs-Return ruft `createNewAbo()`
|
||||
ggf. auf, bevor der Webhook die Stripe-IDs geliefert hat. Für Stripe-Zahlungen legt
|
||||
`createNewAbo()` das Abo daher **nur** an, wenn die IDs bereits an der Zahlung stehen —
|
||||
sonst übernimmt der Webhook-Paid-Pfad die Anlage (einzige Wahrheit, wie bei S3).
|
||||
- **Race Cron-Transaktion vs. Webhook:** Der Abo-Cron bucht **innerhalb einer
|
||||
DB-Transaktion** ab — der Webhook kann eintreffen, bevor die `shopping_payments`-Zeile
|
||||
committet ist. Zwei Sicherungen: (1) `shopping_payments.user_abo_id` markiert
|
||||
Folgeabbuchungen, `createNewAbo()` überspringt sie (kein Duplikat-Abo, falls der Webhook
|
||||
vor dem `user_abo_orders`-Insert des Crons verarbeitet wird); (2) trägt ein Event unsere
|
||||
`metadata.shopping_payment_id`, ist die Zahlung aber (noch) nicht auffindbar, antwortet
|
||||
der Webhook **non-2xx** → Stripe stellt erneut zu, statt das Event als verarbeitet zu
|
||||
begraben. Fremd-Events ohne unsere Metadata werden weiterhin gespeichert + 200.
|
||||
- **Recovery auch im Webhook-Pfad:** `Payment::paymentStatusPaidAction()` ruft für
|
||||
Recovery-Bestellungen jetzt `advanceAboAfterRecoveryPayment()` statt `createNewAbo()`
|
||||
auf (vorher nur im Browser-Return — latenter Duplikat-Abo-Bug, wenn der PAYONE-TSOK
|
||||
vor dem Redirect lief; provider-neutral gefixt).
|
||||
- **Idempotency-Key** der Cron-Abbuchung: `abo-{userAboId}-order-{shoppingOrderId}` —
|
||||
schützt gegen Doppel-Abbuchung bei Netzwerk-Retry innerhalb desselben Laufs.
|
||||
|
||||
### Umsetzungsstatus (2026-07-09)
|
||||
|
||||
| # | Status |
|
||||
|---|--------|
|
||||
| S4.1 | ✅ `initiateCheckoutPayment()`: Abo-/Recovery-Bestellungen → `customer_creation=always` + `payment_intent_data.setup_future_usage=off_session`; Einmalzahlungen unverändert. Zahlarten-Filterung an Stripe delegiert (Dynamic Payment Methods, N1 gelöst). `CheckoutController::stripeCheckoutActive()` ohne Abo-/Recovery-Ausschluss; Abo-Hinweistext (`payment.stripe_abo_hint`) unter der Online-Zahlung. |
|
||||
| S4.2 | ✅ `createNewAbo()`: Stripe-Zweig setzt `payment_provider='stripe'`, `stripe_customer_id`, `stripe_payment_method_id`, `payone_userid=0`; ohne IDs (Browser-Return vor Webhook) wird die Anlage dem Webhook überlassen. Neue Migration `2026_07_09_090000_add_stripe_abo_columns_to_shopping_payments` + `captureOffSessionPaymentMethod()` im Webhook (PI-Event direkt, Event-Store-Backfill, API-Retrieve als letzter Ausweg). |
|
||||
| S4.3 | ✅ `StripePaymentService::chargeAbo()`: off-session PaymentIntent (`customer`, `payment_method`, `off_session`, `confirm`), Idempotency-Key `abo-{aboId}-order-{orderId}`, Transaktion als `authorization` protokolliert, Ergebnis als `AboChargeResult`. |
|
||||
| S4.4 | ✅ `UserMakeOrder::makePayment()`: Dispatch via `providerFor($abo)` — Stripe → `StripeGateway` (neu, Adapter wie PayoneGateway), PAYONE-Zweig byte-identisch; Response-Format (`APPROVED`/`ERROR`/`PENDING`) unverändert für Cron/Retry. |
|
||||
| S4.5 | ✅ Fehler-Mapping: `CardException`/`ApiErrorException` → ERROR (Cron setzt Status 3 + Pay-now-Mail wie bei PAYONE); Kunden-Meldungen für `card_declined`, `insufficient_funds`, `expired_card`, `incorrect_cvc`, `authentication_required` (bestehende `payment.payment_error_reasons.*`-Keys, neu `payment.stripe_abo_*`); PI-Status `processing` → PENDING (next_date bleibt stehen, Webhook schließt ab). |
|
||||
| S4.6 | ✅ `UserAbo::getPaymentType()` Stripe-aware (nutzt `ShoppingPayment::stripeLabelFromSummary()`, neu public static); `UserAbo::formatMaskedCard()` versteht die Stripe-Summary („Visa •••• 4242 (04/2031)"); Portal-Abo-Kopf zeigt bei Stripe die Abo-Nummer statt `#0`. *Offen (kosmetisch → S7.3): Provider-Badge in Admin-Listen, `payone_userid`-Spalte im Admin-Datatable.* |
|
||||
| S4.7 | ✅ implizit: `AboRetryPaymentService` delegiert an `UserMakeOrder::makePayment()` → Dispatch greift. |
|
||||
|
||||
**Zusätzliche Absicherungen (Umsetzung der Vorab-Designentscheidungen):**
|
||||
- `Payment::paymentStatusPaidAction()` behandelt Recovery-Bestellungen jetzt provider-neutral
|
||||
(`advanceAboAfterRecoveryPayment` statt `createNewAbo`) — fixt den latenten
|
||||
Duplikat-Abo-Bug auch für PAYONE.
|
||||
- `createNewAbo()` überspringt Recovery- und Cron-Folgezahlungen (`recovery_user_abo_id` /
|
||||
`shopping_payments.user_abo_id`) grundsätzlich.
|
||||
- Webhook: Event mit unserer `metadata.shopping_payment_id`, dessen Zahlung (noch) nicht
|
||||
sichtbar ist → `503 payment_not_yet_visible` (Stripe-Retry); Fremd-Events weiter `200`.
|
||||
- `advanceAboAfterRecoveryPayment()`: Stripe-Recovery zieht das Abo auf Stripe um
|
||||
(Provider + IDs), PAYONE-Felder bleiben als Historie stehen; ohne IDs wird die
|
||||
Fortschreibung dem Webhook überlassen (Race Browser-Return).
|
||||
|
||||
### Tests (2026-07-09)
|
||||
|
||||
`tests/Feature/StripeAboTest.php` *(neu)*: **15 Tests, 70 Assertions** — Session-Parameter
|
||||
(off-session nur für Abo/Recovery), Abo-Anlage aus dem Webhook (Stripe-Felder, `payone_userid=0`),
|
||||
Browser-Return-Race (Anlage wartet auf Webhook), Cron-Race (kein Duplikat-Abo; 503 → Retry →
|
||||
processed), Recovery-Umzug PAYONE→Stripe, `chargeAbo` (Erfolg inkl. Idempotency-Key,
|
||||
Decline-Mapping, fehlendes Zahlungsmittel ohne API-Call), Cron-Dispatch (APPROVED/ERROR),
|
||||
Anzeige-Labels. `PaymentProviderManagerTest` angepasst (Stripe-Gateway existiert jetzt;
|
||||
Cron-Guard-Test → Dispatch-Test).
|
||||
|
||||
Regression: Stripe-Suite gesamt **59 passed (196 Assertions)**; PAYONE- + CC-Update-Suite
|
||||
**50 passed (172 Assertions)**; Abo-Retry/Recovery **20 passed** — der eine Fehler in
|
||||
`AboPendingInitialPaymentTest` („Session store not set", View-Render mit `Form::open`) ist
|
||||
**vorbestehend auf HEAD** (per `git stash` verifiziert), unabhängig von S4. Blade-Kompilat
|
||||
(`view:cache`) grün, `./vendor/bin/pint --dirty` grün.
|
||||
|
||||
*Abgleich mit dem ursprünglichen Testplan:* Erstzahlung ✅ · Cron-Abbuchung
|
||||
Erfolg/Decline/Idempotenz ✅ · Recovery ✅ · Gemischte Bestände ▶ implizit über
|
||||
Dispatch-Tests + PAYONE-Regression, expliziter Mischlauf gehört in den Testmodus-E2E.
|
||||
|
||||
### Akzeptanzkriterien
|
||||
- [ ] Testmodus Ende-zu-Ende: Abo-Checkout → n Tage später Cron-Abbuchung off-session erfolgreich, Rechnung systemseitig.
|
||||
- [x] Testmodus Ende-zu-Ende ✅ **komplett 2026-07-09** (alles an Abo #442 verprobt):
|
||||
1. Browser-Abo-Abschluss → Abo mit Provider `stripe`, Customer-/PM-ID, Status 2, Order paid via Webhook.
|
||||
2. Cron-Abbuchung `--force` → off-session PI erfolgreich, Order bezahlt, `next_date` fortgeschrieben, kein Duplikat-Abo.
|
||||
3. Abbuchung über per S5-Flow gewechselte Zahlart → erfolgreich (3DS-Karte `…3155`: Setup mit SCA, Folgeabbuchungen als MIT genehmigt — erwartetes Stripe-Verhalten).
|
||||
4. **Decline** (Karte `…0341`) → Order failed, Abo Status 3 (+ Pay-now-Mail).
|
||||
5. **Recovery-Checkout** → Recovery-Order bezahlt (`recovery_user_abo_id` gesetzt), Abo zurück auf Status 2.
|
||||
6. **authentication_required** (Karte `…3184`, verlangt 3DS bei jeder Transaktion) → off-session Decline, Abo Status 3 — der S6.1-Fall mündet korrekt im Recovery-Werkzeug.
|
||||
*(Rechnung systemseitig: auf .test bewusst deaktiviert, Invoice-Gate; Mischlauf PAYONE+Stripe verifiziert sich nach Freeze-Aufhebung in Produktion.)*
|
||||
- [ ] PAYONE-Abos zu 100 % unberührt (Regressionssuite + Stichprobe Staging).
|
||||
- [ ] Nach Freeze-Aufhebung: PAYONE-Bestandsabos buchen wieder ab, neue Abos entstehen mit `payment_provider='stripe'`.
|
||||
|
||||
### Befund aus dem Cron-E2E (2026-07-09): Race trat real auf → `stripe:process-pending-events` (S6.4 vorgezogen)
|
||||
|
||||
Beim ersten echten Cron-Lauf (`user:make_abo_order --abo=442 --force`) trat das in den
|
||||
Designentscheidungen antizipierte **Cron-Race** sofort ein: Der Webhook
|
||||
(`payment_intent.succeeded`) kam ~5 s nach Zahlungsanlage — die Cron-DB-Transaktion war
|
||||
noch offen → korrekt **503 `payment_not_yet_visible`**. Aber: Die **Stripe CLI
|
||||
(`stripe listen`) stellt non-2xx NICHT erneut zu** (anders als der Dashboard-Endpoint in
|
||||
Produktion) → das Event blieb unverarbeitet, die Bestellung „unbezahlt", obwohl die
|
||||
Abbuchung bei Stripe erfolgreich war.
|
||||
|
||||
**Fix (Root Cause, deckt auch Prod-Retry-Latenz ab):** Neues Wartungs-Command
|
||||
**`stripe:process-pending-events`** (`--event=` für Einzelfälle) — verarbeitet
|
||||
gespeicherte Events ohne `processed_at` über `StripeWebhookHandler::processStoredEvent()`
|
||||
nach (Signatur wurde beim Empfang geprüft; weiterhin nicht zuordenbare Events bleiben
|
||||
liegen). Verdrahtung: (1) läuft automatisch **am Ende von `user:make_abo_order`**
|
||||
(alle Zahlungen committet), (2) **Scheduler alle 10 Minuten** (`Kernel.php`,
|
||||
withoutOverlapping). Damit ist S6.4 im Kern vorgezogen; der geplante
|
||||
`stripe:sync-payment-status` (aktiver API-Abgleich bei komplett verlorenem Webhook)
|
||||
bleibt für S6 offen. Nebeneffekt beim ersten Lauf: 4 liegengebliebene
|
||||
`charge.succeeded`-Events vom Go-Live-Tag nachgeholt (Zahlart-Anzeige).
|
||||
|
||||
### Befund aus dem Browser-E2E (2026-07-09): `clearingtype` war varchar(3)
|
||||
|
||||
Beim Verproben von Abo #442 fiel auf: `clearingtype` stand auf **`'str'`** — die Spalten
|
||||
(`shopping_payments`, `user_abos`) waren `varchar(3)` (PAYONE-Kürzel), MySQL hat
|
||||
`'stripe'` **still abgeschnitten** (betraf auch alle Live-Einmalzahlungen seit dem
|
||||
S3-Go-Live; SQLite in der Testsuite erzwingt keine Länge, daher unentdeckt). Kein
|
||||
Logik-Problem — alle Lese-Pfade prüfen `payment_provider`, nie `clearingtype==='stripe'`
|
||||
(per Grep verifiziert). Fix: Migration `2026_07_09_120000_widen_clearingtype_for_stripe`
|
||||
(beide Spalten → varchar(20) + Reparatur der `'str'`-Bestandszeilen; `down()` verkürzt
|
||||
bewusst nicht). Auf der Dev-DB ausgeführt, Abo #442 repariert. **Muss beim
|
||||
Prod-Deployment mitlaufen** (repariert dort die Live-`'str'`-Zeilen automatisch).
|
||||
|
||||
---
|
||||
|
||||
## 9. Phase S5 – Zahlungsmittel-Update über Stripe
|
||||
## 9. Phase S5 – Zahlungsart ändern über Stripe · Status: **ERLEDIGT** (2026-07-09, E2E über CRM + Kunden-Link; nur Portal-Pfad ohne Sichttest)
|
||||
|
||||
### Ziel
|
||||
Der Flow aus dem CC-Update-Projekt (2026-06-25) funktioniert für Stripe-Abos:
|
||||
Zahlungsmittel erneuern **ohne Abbuchung**, nur durch den Abo-Inhaber.
|
||||
**Scope-Erweiterung (Stakeholder 2026-07-09):** Nicht mehr nur „Kreditkarte
|
||||
aktualisieren" — der Abo-Inhaber kann seine **Zahlungsart frei wechseln**: Karte
|
||||
abgelaufen, PayPal-Konto weg → „Zahlungsart ändern" führt auf die Stripe-Hosted-Seite
|
||||
(Session `mode=setup`), dort **freie Wahl** unter allen wiederverwendbaren Zahlarten
|
||||
(Karte, PayPal, SEPA, … — Stripe filtert automatisch); die neue Methode wird **ohne
|
||||
Abbuchung** am Abo verankert und gilt für alle folgenden Abbuchungen. Nur durch den
|
||||
Abo-Inhaber (Policies aus dem CC-Update-Projekt 2026-06-25 unverändert).
|
||||
|
||||
Der Flow gilt für **alle Abos, auch PAYONE-Bestandsabos** (§7.2 Nr. 3): Wer die
|
||||
Zahlungsart ändert, wechselt dabei auf `payment_provider='stripe'` (PAYONE-Felder
|
||||
bleiben als Historie). Das ist die sanfte Auslauf-Migration; eine aktive
|
||||
Migrationskampagne (S8) ist damit nicht mehr geplant. Der bestehende
|
||||
PAYONE-CC-Update-Pfad bleibt im Code erhalten und greift nur noch, solange der
|
||||
Anbieter-Schalter auf `payone` steht.
|
||||
|
||||
### Aufgaben
|
||||
|
||||
| # | Aufgabe |
|
||||
|---|---------|
|
||||
| S5.1 | `StripePaymentService::setupPaymentMethodUpdate()`: Checkout Session `mode=setup` (hosted, Redirect – konsistent zum Checkout-Flow); Einstieg über die bestehenden Card-Update-Views/Links (Verzweigung nach Abo-Provider). |
|
||||
| S5.2 | `setup_intent.succeeded`-Webhook bzw. Return-Bestätigung: neue `stripe_payment_method_id` am Abo speichern, alte PaymentMethod am Customer detachen; `user_abo_card_changes`-Protokoll weiternutzen. |
|
||||
| S5.3 | Konsistenz-Regeln aus dem CC-Update-Projekt übernehmen: `card_update_pending_at` vor externem Call setzen; API-OK → DB-Update; Fehlerfall → CRITICAL-Log + Incident (Alt-Plan §5.3.1). |
|
||||
| S5.4 | `AboPaymentMethodService` provider-fähig; `AboCardUpdateLink`-Flow (Berater→Kunde-Link) für Stripe-Abos freischalten. |
|
||||
| S5.1 | `StripePaymentService::setupPaymentMethodChange()`: Checkout Session `mode=setup` (hosted, Redirect — konsistent zum Checkout-Flow) mit Customer (vorhandene `stripe_customer_id` des Abos oder neu via `StripeCustomerService`/Abo-Daten) und `setup_intent_data.metadata.user_abo_id`; freie Zahlartenwahl bei Stripe. |
|
||||
| S5.2 | Webhook `checkout.session.completed` (`mode=setup`) / `setup_intent.succeeded`: Abo via `metadata.user_abo_id` auflösen → `payment_provider='stripe'`, `stripe_customer_id`, `stripe_payment_method_id`, `clearingtype='stripe'`, Anzeige-Summary (`carddata`) aus der PaymentMethod; alte Stripe-PaymentMethod am Customer detachen (best effort); `user_abo_card_changes`-Protokoll weiternutzen. |
|
||||
| S5.3 | Konsistenz-Regeln aus dem CC-Update-Projekt übernehmen: `card_update_pending_at` vor Redirect setzen; Webhook-OK → DB-Update + Flag löschen; Fehlerfall → CRITICAL-Log (Alt-Plan §5.3.1). |
|
||||
| S5.4 | Einstiege umstellen (Verzweigung: Schalter `stripe` → Stripe-Setup-Flow, sonst bestehender PAYONE-CC-Pfad): Portal „Zahlungsart ändern", CRM (Berater-eigenes Abo), `AboCardUpdateLink`-Flow (Berater→Kunde-Link) — Formular-Partial zeigt statt Kartenfeldern einen „Zahlungsart bei Stripe ändern"-Button; gilt für PAYONE- UND Stripe-Abos. |
|
||||
|
||||
### Tests
|
||||
- SetupIntent-Erfolg aktualisiert nur das Ziel-Abo; keine Abbuchung entsteht.
|
||||
- Nicht-Inhaber wird abgewiesen (Policy unverändert).
|
||||
- Pending-Flag/Inkonsistenz-Pfad (API ok, DB-Fehler) erzeugt Incident.
|
||||
### Umsetzungsstatus (2026-07-09)
|
||||
|
||||
| # | Status |
|
||||
|---|--------|
|
||||
| S5.1 | ✅ `StripePaymentService::createPaymentMethodChangeSession()` (mode=setup, Metadata `user_abo_id`/`source` auf Session + SetupIntent); `ensureAboStripeCustomer()` legt für PAYONE-Bestandsabos den Stripe-Customer an und persistiert ihn sofort (Provider bleibt PAYONE bis zur Verankerung). |
|
||||
| S5.2 | ✅ Return-Pfad (`finalizeStripeChangeFromSession`, prüft Session-Zugehörigkeit via Metadata) UND Webhook `setup_intent.succeeded` rufen das idempotente `applyStripePaymentMethod()`: Provider→stripe, Customer-/PM-ID, `clearingtype='stripe'`, Anzeige-Summary (`retrievePaymentMethodSummary`), `user_abo_card_changes`-Eintrag; alte Stripe-PM wird best effort detached. |
|
||||
| S5.3 | ✅ `card_update_pending_at` vor Session-Erstellung; Fehler → Spur zurück; DB-Write-Fehler nach Setup-Erfolg → CRITICAL-Log, Spur bleibt (Muster CC-Update-Projekt). Freeze-Schalter blockiert `beginStripeChange`. |
|
||||
| S5.4 | ✅ Drei Einstiege verzweigen nach Schalter: Portal (`portal_abos_payment_method_stripe`), CRM me-Abo (`user_abos_payment_method_stripe`), Kunden-Link (`abo_card_update_stripe`); gemeinsames Partial `_payment_method_form` zeigt bei Stripe einen „Neue Zahlungsart wählen"-Button statt Kartenfeldern; Rückkehr über die bestehenden `…_return`-Routen (`?session_id={CHECKOUT_SESSION_ID}`). **Invariante sauber getrennt:** Model bleibt pur (`canUpdatePaymentMethod` = nur CC; neu `canChangePaymentMethodAnyType`), die Flow-Wahl liegt in `AboPaymentMethodService::canStartChange()` (Controller + Views umgestellt). `sendCardUpdateLink` erlaubt den Link im Stripe-Flow für jede Zahlart. Abo-Detail-Karten (CRM/Portal) zeigen die Zahlart jetzt via `getPaymentType()` auch für Nicht-CC-/Stripe-Abos. |
|
||||
|
||||
**Neue Lang-Keys:** `abo.payment_method_change_*`, `abo.current_payment_method` (de/en, du-Form).
|
||||
Der PAYONE-CC-Pfad ist unverändert und greift bei Schalter `payone`.
|
||||
|
||||
### Tests (2026-07-09)
|
||||
|
||||
`tests/Feature/StripePaymentMethodChangeTest.php` *(neu)*: **10 Tests** —
|
||||
*(Fixes nach Manualtests 2026-07-09: (1) Setup-Sessions mit dynamischen Zahlarten
|
||||
verlangen `currency` — ergänzt, fest `eur`; (2) Erfolgs-/Fehlerseite des Kunden-Links
|
||||
(`card_update_done`) und die Link-Mail (`AboCardUpdateLinkMail`) sprachen noch von
|
||||
„Kreditkarte"/PAYONE — Texte jetzt flow-abhängig: „Zahlungsart aktualisiert" bzw.
|
||||
Stripe-Wortlaut, neue Keys `abo.payment_method_change_done_*`/`…_link_mail_*`.)* —
|
||||
Flow-Weiche (Nicht-CC-Abos nur im Stripe-Flow, terminale Abos nie), Setup-Session
|
||||
(Params, Pending-Spur), Customer-Anlage für PAYONE-Abos (Provider bleibt payone),
|
||||
Freeze-Guard, Verankern (PAYONE→Stripe-Umzug inkl. Anzeige/`card_changes`), Idempotenz +
|
||||
Detach der alten PM, Webhook-Verankerung, Return-Pfad inkl. Abweisung fremder Sessions.
|
||||
Regression: Stripe-Gesamtsuite + CC-Update- + Abo-Retry/Recovery-Suiten **99 passed
|
||||
(303 Assertions)**, Blade-Kompilat grün, Pint grün.
|
||||
|
||||
*Ursprünglicher Testplan (Referenz):* Setup ohne Abbuchung ✅ · Nicht-Inhaber abgewiesen ✅
|
||||
(Policies unverändert, per bestehender Suite) · Pending-/Inkonsistenz-Pfad ✅ (CRITICAL-Log;
|
||||
Incident-Anbindung folgt mit S6.2).
|
||||
|
||||
### Akzeptanzkriterien
|
||||
- [x] **Browser-E2E (Testmodus), CRM-Pfad:** ✅ 2026-07-09 — Abo #442, neue Karte über die Setup-Seite hinterlegt: neue PM-ID verankert, carddata „Visa •••• 3155", `card_update_pending_at` geräumt, **genau ein** `user_abo_card_changes`-Eintrag (source `crm` — Return-Pfad und Webhook liefen beide, Idempotenz griff), keine Abbuchung.
|
||||
- [x] **Browser-E2E, Berater-Kunden-Link inkl. Nicht-Karten-Zahlart:** ✅ 2026-07-09 — Abo #440 über den Link auf **PayPal** umgestellt: Provider `stripe`, PM-ID verankert, carddata `{type: paypal}`, Link entwertet (`used_at`), Wechsel-Protokoll source `link`, Status 2. Dabei gefixt: Erfolgsseite + Link-Mail sprachen noch von „Kreditkarte" → Texte jetzt flow-abhängig („Zahlungsart aktualisiert"). *Offen: Portal-Pfad (gleicher Code-Pfad wie CRM, geringe Priorität).*
|
||||
- [x] **PAYONE-Bestandsabo wechselt vollständig auf Stripe** (Felder + Provider), PAYONE-Felder bleiben als Historie: ✅ Abo #440 war ein PAYONE-CC-Abo (Wechsel-Historie vorhanden) und läuft jetzt über Stripe/PayPal.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -512,7 +696,7 @@ Payment-Dashboard kennt Stripe.
|
|||
| S6.1 | `authentication_required` bei off-session: Abo auf Status 3 + Recovery-Checkout-Link (bestehender `AboRecoveryCheckoutService`); Recovery-Zahlung läuft on-session mit 3DS über den **Abo-Provider**; nach Erfolg `advanceAboAfterRecoveryPayment` wie gehabt. |
|
||||
| S6.2 | `payment_intent.payment_failed` / `charge.dispute.created` / `charge.refunded` → `PaymentIncident` (Provider `stripe`) + Payment-Dashboard-Kacheln. |
|
||||
| S6.3 | `payments:check-accounts` bzw. Uptime-Check um Stripe-Status ergänzen (API-Erreichbarkeit, offene `processing`-Zahlungen älter X Tage). |
|
||||
| S6.4 | Wartungs-Command `stripe:sync-payment-status {paymentIntentId|--pending}` (Gegenstück zu `payone:*`-Reparatur-Commands): Status aktiv nachziehen, wenn ein Webhook verloren ging. |
|
||||
| S6.4 | Wartungs-Command `stripe:sync-payment-status {paymentIntentId|--pending}` (Gegenstück zu `payone:*`-Reparatur-Commands): Status aktiv nachziehen, wenn ein Webhook verloren ging. **Teilweise vorgezogen (2026-07-09, s. §8):** `stripe:process-pending-events` verarbeitet gespeicherte, unverarbeitete Events nach (Cron-Ende + Scheduler alle 10 min). Offen bleibt der aktive API-Abgleich für komplett verlorene Webhooks. |
|
||||
|
||||
### Tests
|
||||
- Failed-Webhook setzt Abo-/Order-Status korrekt, erzeugt Incident, keine Doppel-Mails.
|
||||
|
|
@ -529,8 +713,55 @@ Payment-Dashboard kennt Stripe.
|
|||
| S7.1 | **Schalter-UI** im Admin (Einstellungen): aktueller Provider, Umschalten mit Bestätigungsdialog + Hinweistext („wirkt nur auf neue Zahlungen"), `SysLog`-Eintrag. Nur berechtigte Admin-Rolle. |
|
||||
| S7.2 | **Refunds:** `OrderWithdrawalController` (Widerruf) um automatische Erstattung erweitern — Provider der Ursprungszahlung entscheidet (Stripe: `Refund`-API; PAYONE: bestehender/manueller Weg). Teil-Erstattungen unterstützen. |
|
||||
| S7.3 | Admin-Detailansichten (`ShoppingOrder`, `UserAbo`, Zahlungen): Provider-Badge, Stripe-IDs verlinkt zum Stripe-Dashboard (Deep-Link `https://dashboard.stripe.com/...`). |
|
||||
| S7.4 | Payment-Dashboard: Umsatz/Fehlerraten je Provider; Übersicht „Abos je Provider" (Fortschritt einer späteren Migration ablesbar). |
|
||||
| S7.4 | Payment-Dashboard (`/admin/payment-dashboard`) **für Stripe anpassen/optimieren** (Stakeholder 2026-07-09): Kacheln/Listen kennen bisher nur PAYONE — Stripe-Zahlungen korrekt ausweisen (Provider-Filter, Zahlart-Labels, Verlinkung ins Stripe-Dashboard), Umsatz/Fehlerraten je Provider, Übersicht „Abos je Provider" (Auslauf-Fortschritt ablesbar). |
|
||||
| S7.5 | Doku: Betriebs-Runbook (Webhook neu ausliefern, Key-Rotation, Testmodus) in diesem Ordner ablegen. |
|
||||
| S7.6 | **Zahlarten-Kommunikation** (Stakeholder 2026-07-09): Die über Stripe verfügbaren Zahlarten klar benennen, möglichst mit Logos — (a) im Checkout unter der „Online-Zahlung"-Option, (b) auf den Shop-Seiten, (c) im Footer bei den Zahlungsmöglichkeiten, (d) auf der Seite `/zahlungsarten`. Vorkasse als „Vorkasse (PAYONE)" ausweisen. **ERLEDIGT ✅ (2026-07-09), siehe Umsetzungsstatus unten.** |
|
||||
|
||||
### Umsetzungsstatus S7.6 (ERLEDIGT ✅, 2026-07-09)
|
||||
|
||||
Kommuniziertes Zahlarten-Set (per API gegen die aktive Stripe `PaymentMethodConfiguration`
|
||||
und die Account-Capabilities verifiziert, Erweiterung auf Stakeholder-Wunsch 2026-07-09):
|
||||
**Kreditkarte (Visa, Mastercard, American Express), PayPal, Apple Pay, Google Pay,
|
||||
Klarna, Amazon Pay, Link (Stripe), EPS, Bancontact** sowie als Sammelgruppe „Weitere
|
||||
Zahlarten": **BLIK, MB WAY, Pix, Satispay, Samsung Pay, Kakao Pay, Naver Pay, PAYCO** —
|
||||
plus **Vorkasse (PAYONE)**. Sofortüberweisung (von Stripe eingestellt) und
|
||||
SEPA-Lastschrift (nicht aktiviert) wurden von der `/zahlungsarten`-Seite und aus dem
|
||||
Footer **entfernt**. ⚠️ Verifiziert gegen den **Test-Modus** — Test-/Live-Modus haben
|
||||
getrennte Zahlarten-Konfigurationen; das Live-Dashboard sollte dasselbe Set anbieten.
|
||||
|
||||
- **Logo-Assets:** einheitliche Marken-SVGs (38×24-Kartenstil, Shopify-`payment_icons`-Set)
|
||||
unter `public/images/payments-assets/`: `visa/mastercard/amex/paypal/applepay/googlepay/
|
||||
klarna/amazonpay/eps/bancontact/blik/mbway/pix/satispay/samsungpay/kakaopay/naverpay/
|
||||
payco.svg` sowie selbst erstellte Badges `vorkasse.svg` („€ →") und `link.svg`
|
||||
(grünes Stripe-Link-Badge; kein Icon im Set verfügbar). Die alten PNGs bleiben
|
||||
liegen (Checkout-Badges `stripe.png`/`vor.png`/`fnc.png` werden weiter genutzt).
|
||||
- **Wiederverwendbares Partial:** `resources/views/web/layouts/includes/payment_logos.blade.php`
|
||||
(Parameter `logoHeight`, `showVorkasse`, `showAll`, `wrapperClass`; `title`/`alt` je
|
||||
Marke) — genutzt von Checkout, beiden Footern und `/zahlungsarten`. Standard
|
||||
(Checkout/Footer): nur die Kern-Zahlarten + „···"-Icon (`more.svg`, Tooltip „Weitere
|
||||
Zahlarten"); die regionalen Zahlarten (BLIK/MB WAY/Pix/…) erscheinen nur mit
|
||||
`showAll = true` auf `/zahlungsarten` (Stakeholder-Feedback 2026-07-09).
|
||||
- **(a) Checkout** (`web/templates/checkout.blade.php`): Logo-Leiste (ohne Vorkasse) unter
|
||||
der Option „Online-Zahlung", darunter weiterhin die benannte Liste
|
||||
(`payment.stripe_online_methods`, um Amazon Pay ergänzt). Vorkasse war bereits als
|
||||
„Vorkasse (PAYONE)" ausgewiesen (S4).
|
||||
- **(b) Shop-Seiten / (c) Footer:** beide Footer (`web/layouts/includes/footer.blade.php`,
|
||||
`web/user/layouts/includes/footer.blade.php`) zeigen statt der 5 veralteten PNGs
|
||||
(PayPal/Sofort/SEPA/CC/Vorkasse) die neue Logo-Leiste inkl. Vorkasse — damit auf allen
|
||||
Shop-Seiten sichtbar.
|
||||
- **(d) `/zahlungsarten`** (`web/templates/zahlungsarten.blade.php`): komplett neu —
|
||||
Intro (Stripe als Zahlungsdienstleister) + Logo-Leiste, dann je Zahlart eine Zeile mit
|
||||
Logo, Name und Kurzbeschreibung: Kreditkarte, PayPal, Apple Pay, Google Pay, Klarna,
|
||||
Amazon Pay, Link (Stripe), EPS, Bancontact, Sammelzeile „Weitere Zahlarten"
|
||||
(BLIK/MB WAY/Pix/Satispay/Samsung Pay/Kakao Pay/Naver Pay/PAYCO), Vorkasse (PAYONE).
|
||||
Neue Lang-Keys `weborder.payment_method_*` in de (du-Form) und en; alte
|
||||
Sie-Form-Texte ersetzt, `payment_method_direct*`/`_sepa*` entfernt (waren nur dort
|
||||
verwendet).
|
||||
- **Tests:** `tests/Feature/PaymentMethodsCommunicationTest.php` (27 Tests: Partial mit/ohne
|
||||
Vorkasse, alle SVG-Assets vorhanden, alle de/en-Lang-Keys, „Vorkasse (PAYONE)"-Label).
|
||||
- **Hinweis:** Die Seite ist statisch — sollte der Provider-Schalter im Notfall zurück auf
|
||||
`payone` gestellt werden, stimmt die Kommunikation vorübergehend nicht (bewusst in Kauf
|
||||
genommen; der Checkout selbst bleibt maßgeblich).
|
||||
|
||||
### Tests
|
||||
- Schalter-UI: Berechtigung, Persistenz, SysLog.
|
||||
|
|
@ -554,17 +785,37 @@ Payment-Dashboard kennt Stripe.
|
|||
Provider trägt, entstehen keine Mischzustände. Offene Stripe-Zahlungen laufen über
|
||||
Webhooks normal aus.
|
||||
|
||||
### Optionale Abo-Migration PAYONE → Stripe (nur nach Entscheid O8)
|
||||
### Optionale Abo-Migration PAYONE → Stripe
|
||||
|
||||
> **Entscheid 2026-07-09 (§7.2): Auslaufmodell statt Kampagne.** PAYONE ist wieder
|
||||
> aktiv, Bestandsabos laufen dort unbefristet weiter. Die „Migration" passiert passiv
|
||||
> über die Zahlungsmittel-Änderung (S4 Recovery / S5 CC-Update) — eine aktive
|
||||
> Einladungs-Kampagne (`abo:invite-stripe-migration`) ist **nicht mehr geplant** und
|
||||
> bleibt hier nur als Option dokumentiert, falls PAYONE doch abgeschaltet werden soll.
|
||||
|
||||
- PAYONE-Tokens sind **nicht portabel** → Migration heißt: Kunde hinterlegt Zahlungsmittel
|
||||
neu (SetupIntent, kein Charge) über den Card-Update-Link-Flow; danach
|
||||
`payment_provider='stripe'` am Abo, PAYONE-Felder bleiben als Historie.
|
||||
- Kampagnen-Command `abo:invite-stripe-migration` (Batch-Mails mit Link, Erinnerung,
|
||||
Fortschritts-Report) — Muster: Reminder-Commands aus dem Einmalprodukte-Projekt.
|
||||
- Kein Zwang, kein Big-Bang: nicht migrierte Abos laufen unbefristet über PAYONE.
|
||||
|
||||
---
|
||||
|
||||
## 12.1 Phase S9 (nachgelagert) – Rückbuchungen & Stornierungen erfassen
|
||||
|
||||
**Stakeholder-Entscheid 2026-07-09:** Als eigener Punkt aufgenommen, bewusst
|
||||
**nachgelagert** (nach S4–S7). Zukünftig müssen Rückbuchungen und Stornierungen
|
||||
systemseitig miterfasst werden:
|
||||
|
||||
| # | Aufgabe |
|
||||
|---|---------|
|
||||
| S9.1 | **Rückbuchungen (Chargebacks/Disputes):** `charge.dispute.created`/`charge.dispute.closed` verarbeiten — Zahlung/Bestellung kennzeichnen, `PaymentIncident` erzeugen (Überschneidung S6.2), Betrag inkl. Dispute-Gebühr ausweisen. PAYONE-Pendant (Rückläufer/`cancelation`) mitdenken. |
|
||||
| S9.2 | **Erstattungen:** `charge.refunded` (Voll/Teil) an der Zahlung verbuchen; Verzahnung mit dem Widerrufs-/Refund-Flow aus S7.2. |
|
||||
| S9.3 | **Stornierungen:** Stripe-seitige Cancels (`payment_intent.canceled`, abgebrochene/stornierte Vorgänge) sauber am Vorgang dokumentieren. |
|
||||
| S9.4 | **Buchhaltung:** Klären, wie Rückbuchungen/Storni in Rechnungskorrektur/DATEV-Export einfließen (mit Stakeholder/Steuerberater). |
|
||||
| S9.5 | Auswertung: Kacheln/Listen im Payment-Dashboard (Überschneidung S7.4). |
|
||||
|
||||
---
|
||||
|
||||
## 13. Teststrategie (übergreifend)
|
||||
|
||||
| Ebene | Inhalt |
|
||||
|
|
@ -627,13 +878,27 @@ routes/api.php, routes/domains/*.php
|
|||
Abo-/Order-Views (Provider-Badge, maskiertes Zahlungsmittel)
|
||||
```
|
||||
|
||||
## 16. Nächste Schritte
|
||||
## 16. Nächste Schritte (Stand 2026-07-09)
|
||||
|
||||
1. ~~O-Fragen klären~~ ✅ **beantwortet 2026-07-08** (Konzept §10): Hosted Checkout mit
|
||||
Redirect, alle Stripe-Zahlarten inkl. PayPal/Apple Pay/Google Pay/Amazon Pay/Klarna,
|
||||
nur EUR, SDK freigegeben, Account vorhanden, Bestands-Abo-Migration und
|
||||
Membership/ELV bewusst nach hinten gestellt (S8 bzw. Folgeprojekt).
|
||||
2. Phase **S0** starten: Test-Keys + Webhook im (vorhandenen) Stripe-Account einrichten,
|
||||
`stripe/stripe-php` installieren, `config/stripe.php`, **N1-Prüfung** (Klarna/Amazon
|
||||
Pay Abo-Fähigkeit).
|
||||
3. Danach S1 → S2 → S3; nach S3 erster produktiver Meilenstein (Einmalzahlung) möglich.
|
||||
1. ~~**Testmodus-E2E für S4/S5**~~ ✅ **komplett 2026-07-09**: Abo-Abschluss, Cron-Abbuchung
|
||||
(inkl. `stripe:process-pending-events`), Zahlungsart-Wechsel (CRM + Kunden-Link,
|
||||
PayPal, PAYONE→Stripe-Umzug), Decline → Status 3, Recovery-Checkout,
|
||||
authentication_required. Aufräumen: Test-Abos #440/#442 (Abo 442 steht nach dem
|
||||
letzten Test auf Status 3 mit der `…3184`-Testkarte) und Test-Bestellungen bereinigen.
|
||||
2. **Deployment + Migrationen** (3 neue: Provider-/Stripe-Abo-Spalten,
|
||||
`widen_clearingtype`), dann im Admin: **`abo-payments-frozen` aufheben** (§7.2),
|
||||
Vorkasse wieder einblenden (`payment-vorkasse-hidden` aus). Ersten 04:00-Cron-Lauf
|
||||
beobachten (holt eingefrorene PAYONE-Abbuchungen nach → großer Lauf!):
|
||||
`abo_order.log`, `stripe.log`, `stripe_webhook_events`, Payment-Dashboard.
|
||||
Der Mischlauf PAYONE+Stripe verifiziert sich dabei. Dazu im **Live**-Stripe-Dashboard
|
||||
das Zahlarten-Set mit dem Testmodus abgleichen (S7.6 kommuniziert u. a. Link, EPS,
|
||||
Bancontact — Test/Live sind getrennt konfiguriert!).
|
||||
3. ~~Phase **S5**~~ ✅ umgesetzt + E2E (2026-07-09, Scope erweitert auf „Zahlungsart
|
||||
ändern"): CRM-Pfad und Berater-Kunden-Link im Browser verprobt (inkl. PayPal und
|
||||
PAYONE→Stripe-Umzug, Abo 440). Nur der **Portal-Pfad** ist ohne Sichttest
|
||||
(identischer Code-Pfad, geringe Prio).
|
||||
4. ~~**S7.6 Zahlarten-Kommunikation**~~ ✅ umgesetzt (2026-07-09, siehe Umsetzungsstatus
|
||||
in §11): Logos + Benennung in Checkout, Footer (alle Shop-Seiten) und `/zahlungsarten`.
|
||||
**Offen:** kurzer Sichttest im Browser (Footer, `/zahlungsarten`, Checkout).
|
||||
5. **S7.4 Payment-Dashboard** für Stripe anpassen/optimieren (Stakeholder 2026-07-09).
|
||||
6. Nachgelagert: **S9 Rückbuchungen & Stornierungen** (§12.1).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue