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:
Kevin 2026-07-09 14:45:17 +00:00
parent 6f21845607
commit ce4750fd1a
68 changed files with 2856 additions and 236 deletions

View file

@ -141,23 +141,24 @@
@include('admin.abo._executions')
</div>
@if ($user_abo->isCreditCard())
@php($canStartPaymentChange = app(\App\Services\AboPaymentMethodService::class)->canStartChange($user_abo))
@if ($user_abo->isCreditCard() || $canStartPaymentChange)
<div class="card mt-3 mb-3">
<div class="card-body">
<h6 class="font-weight-bold">{{ __('abo.payment_method') }}</h6>
<p class="mb-2">
{{ __('payment.credit_card') }}@if ($user_abo->maskedCard())
{{ $user_abo->getPaymentType() }}@if ($user_abo->maskedCard())
: {{ $user_abo->maskedCard() }}
@endif
</p>
@if ($view === 'me' && $user_abo->canUpdatePaymentMethod())
@if ($view === 'me' && $canStartPaymentChange)
<a href="{{ route('user_abos_payment_method_edit', [$view, $user_abo->id]) }}"
class="btn btn-sm btn-primary">
{{ __('abo.update_payment_method') }}
</a>
@endif
@if ($view === 'ot' && $user_abo->canUpdatePaymentMethod())
@if ($view === 'ot' && $canStartPaymentChange)
<form method="POST" action="{{ route('user_abos_payment_method_send_link', [$view, $user_abo->id]) }}"
class="d-inline">
@csrf

View file

@ -3,13 +3,14 @@
@section('content')
<h4 class="font-weight-bold py-2 mb-2">
{{ __('abo.update_card_title') }}
<span class="text-muted">{{ '#' . $user_abo->payone_userid }}</span>
{{ app(\App\Services\AboPaymentMethodService::class)->usesStripeChange() ? __('abo.payment_method_change_title') : __('abo.update_card_title') }}
<span class="text-muted">{{ '#' . ($user_abo->payone_userid ?: $user_abo->id) }}</span>
</h4>
@include('portal.abo._payment_method_form', [
'user_abo' => $user_abo,
'formAction' => route('user_abos_payment_method_update', [$view, $user_abo->id]),
'stripeFormAction' => route('user_abos_payment_method_stripe', [$view, $user_abo->id]),
'backUrl' => route('user_abos_detail', [$view, $user_abo->id]),
])