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>
67 lines
2.6 KiB
PHP
67 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Payment\Stripe\DTOs;
|
|
|
|
/**
|
|
* Ergebnis der Stripe-Webhook-Verarbeitung. Anders als beim PAYONE-IPN
|
|
* (immer "TSOK") steuert der HTTP-Status das Retry-Verhalten von Stripe:
|
|
* 2xx = erledigt, alles andere wird erneut zugestellt.
|
|
*/
|
|
class StripeWebhookResult
|
|
{
|
|
private function __construct(
|
|
public readonly int $statusCode,
|
|
public readonly string $state,
|
|
public readonly ?string $eventId = null,
|
|
public readonly ?string $eventType = null,
|
|
public readonly ?string $reason = null,
|
|
) {}
|
|
|
|
/** Signatur ungültig oder Payload kein gültiges Event — kein Retry sinnvoll. */
|
|
public static function invalidSignature(string $reason): self
|
|
{
|
|
return new self(statusCode: 400, state: 'invalid_signature', reason: $reason);
|
|
}
|
|
|
|
/** STRIPE_WEBHOOK_SECRET fehlt — 500, damit Stripe später erneut zustellt. */
|
|
public static function notConfigured(): self
|
|
{
|
|
return new self(statusCode: 500, state: 'not_configured', reason: 'STRIPE_WEBHOOK_SECRET missing');
|
|
}
|
|
|
|
/** Event-ID bereits verarbeitet — Retry wird bestätigt, aber ignoriert. */
|
|
public static function duplicate(string $eventId, string $eventType): self
|
|
{
|
|
return new self(statusCode: 200, state: 'duplicate', eventId: $eventId, eventType: $eventType);
|
|
}
|
|
|
|
/** Gültiges Event eines nicht behandelten Typs — protokolliert, bestätigt. */
|
|
public static function ignored(string $eventId, string $eventType): self
|
|
{
|
|
return new self(statusCode: 200, state: 'ignored', eventId: $eventId, eventType: $eventType);
|
|
}
|
|
|
|
/** Event verarbeitet (Transaktion protokolliert, Folgeaktionen angestoßen). */
|
|
public static function processed(string $eventId, string $eventType): self
|
|
{
|
|
return new self(statusCode: 200, state: 'processed', eventId: $eventId, eventType: $eventType);
|
|
}
|
|
|
|
/**
|
|
* Event trägt unsere Metadata (shopping_payment_id), aber die Zahlung ist
|
|
* (noch) nicht auffindbar — z. B. weil die Cron-DB-Transaktion noch nicht
|
|
* committet war (Plan S4, Race Cron vs. Webhook). Non-2xx → Stripe stellt
|
|
* erneut zu; das gespeicherte Event bleibt unverarbeitet und wird beim
|
|
* Retry regulär verarbeitet.
|
|
*/
|
|
public static function paymentNotYetVisible(string $eventId, string $eventType): self
|
|
{
|
|
return new self(
|
|
statusCode: 503,
|
|
state: 'payment_not_yet_visible',
|
|
eventId: $eventId,
|
|
eventType: $eventType,
|
|
reason: 'shopping_payment aus metadata (noch) nicht auffindbar — Retry angefordert',
|
|
);
|
|
}
|
|
}
|