Stripe-Integration (S0–S3): Einmalzahlungen über Stripe Checkout + Notfall-Go-Live
Wegen PAYONE-Ausfall vorgezogen live geschaltet: alle Einmalzahlungen (Shop + Salescenter) laufen über Stripe Hosted Checkout, Abos sind bis Phase S4 eingefroren. - Provider-Abstraktion: PaymentProvider-Enum, PaymentGatewayInterface, PaymentProviderManager (Settings-Schalter, provider-treu je Bestand), PayoneGateway als unveränderter Adapter - StripePaymentService: Checkout Session (eine Gesamtposition, Metadata, success/cancel-Redirects), StripeClientWrapper (lazy, Dark Launch), StripeCustomerService, StripeTransactionRecorder (client_secret-Redaction) - Webhook /stripe/webhook: Signaturprüfung, Idempotenz über stripe_webhook_events (Crash-Retry-Reprocessing), txaction-Prioritäten, Betrags-Guard, lockForUpdate-Paid-Pfad, genau eine Bestätigungsmail - Zahlungsmethoden-Anzeige aus charge.succeeded (carddata inkl. Wallet, auftragsunabhängiges Backfill aus dem Event-Store) - Checkout: Wartungsmodus mit Admin-Bypass, Abo-Freeze-Hinweis, Stripe-Option (Online-Zahlung) statt Zahlartenwahl, Vorkasse ausblendbar - Abo-Freeze-Guards: Cron user:make_abo_order, Retry, Kartenupdate - Admin-Settings-Karte Zahlungen (Provider, Wartung, Freeze, Vorkasse) - Hotfix: 500 bei Vorkasse ohne Clearing-Daten in admin/sales/_detail und checkout-final (PAYONE-Ausfall) - Doku dev/2026-07-08-stripe/ (Plan S0–S8, Konzept, IST-Analyse) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1108381988
commit
e1da90ff91
48 changed files with 4428 additions and 831 deletions
|
|
@ -149,6 +149,21 @@ return [
|
|||
'commission' => 'Provision',
|
||||
'unknown' => 'unbekannt',
|
||||
|
||||
// Stripe (Hosted Checkout)
|
||||
'stripe_online' => 'Online-Zahlung',
|
||||
'stripe_online_methods' => 'Kreditkarte, PayPal, Apple Pay, Google Pay, Klarna u. v. m.',
|
||||
'stripe_online_copy' => 'Du wirst zur sicheren Zahlungsseite weitergeleitet und wählst dort deine Zahlungsart.',
|
||||
'stripe_online_short' => 'Online-Zahlung (Stripe)',
|
||||
'stripe_unavailable' => 'Die Online-Zahlung ist derzeit nicht erreichbar. Bitte versuche es gleich erneut.',
|
||||
'stripe_order_label' => 'Bestellung :reference',
|
||||
|
||||
// Not-Schalter: Abo-Zahlungen eingefroren (PAYONE-Ausfall, bis Stripe-Abos live sind)
|
||||
'prepayment_no_clearing_data' => 'Bankverbindung liegt noch nicht vor — die Zahlung wurde beim Zahlungsanbieter noch nicht erfolgreich registriert.',
|
||||
'checkout_maintenance_title' => 'Technische Schwierigkeiten',
|
||||
'checkout_maintenance' => 'Aktuell sind Bestellungen wegen technischer Schwierigkeiten leider nicht möglich. Wir sind in Kürze wieder für dich da — bitte versuche es später erneut.',
|
||||
'abo_payments_frozen_title' => 'Abo-Zahlungen vorübergehend nicht möglich',
|
||||
'abo_payments_frozen' => 'Abo-Zahlungen sind derzeit leider nicht möglich. Wir arbeiten mit Hochdruck an einer technischen Lösung — bitte versuche es später erneut. Deine Abos und Bestellungen gehen dabei nicht verloren.',
|
||||
|
||||
// Vorkasse Verwendungszweck Hinweise
|
||||
'prepayment_important_notice' => 'Wichtiger Hinweis zur Vorkasse-Zahlung',
|
||||
'prepayment_reference_notice' => 'WICHTIG: Bitte geben Sie als Verwendungszweck ausschließlich folgende Nummer an:',
|
||||
|
|
|
|||
|
|
@ -196,4 +196,19 @@ return [
|
|||
'' => '',
|
||||
'American Express' => 'American Express',
|
||||
'unknown' => 'unknown',
|
||||
|
||||
// Stripe (hosted checkout)
|
||||
'stripe_online' => 'Online payment',
|
||||
'stripe_online_methods' => 'Credit card, PayPal, Apple Pay, Google Pay, Klarna and more',
|
||||
'stripe_online_copy' => 'You will be redirected to a secure payment page where you choose your payment method.',
|
||||
'stripe_online_short' => 'Online payment (Stripe)',
|
||||
'stripe_unavailable' => 'Online payment is currently unavailable. Please try again in a moment.',
|
||||
'stripe_order_label' => 'Order :reference',
|
||||
|
||||
// Emergency switch: subscription payments frozen (PAYONE outage, until Stripe subscriptions are live)
|
||||
'prepayment_no_clearing_data' => 'Bank details are not available yet — the payment has not been successfully registered with the payment provider.',
|
||||
'checkout_maintenance_title' => 'Technical difficulties',
|
||||
'checkout_maintenance' => 'Ordering is currently not possible due to technical difficulties. We will be back shortly — please try again later.',
|
||||
'abo_payments_frozen_title' => 'Subscription payments temporarily unavailable',
|
||||
'abo_payments_frozen' => 'Subscription payments are currently not possible. We are working hard on a technical solution — please try again later. Your subscriptions and orders will not be lost.',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -363,29 +363,31 @@
|
|||
<div class="alert alert-warning border-warning mb-3" style="border-left: 4px solid #ffc107 !important;">
|
||||
<h5 class="mb-3"><i class="fa fa-university"></i> {{ __('payment.prepayment_bank_details') }}:</h5>
|
||||
<div style="background: #fff; border-radius: 8px; padding: 15px;">
|
||||
@if($payTrans && isset($payTrans->transmitted_data))
|
||||
@if($payTrans && isset($payTrans->transmitted_data) && !empty($payTrans->transmitted_data['clearing_bankiban']))
|
||||
<table style="width: 100%;">
|
||||
<tr>
|
||||
<td style="padding: 5px 10px 5px 0; width: 140px;"><strong>{{ __('payment.account_holder') }}:</strong></td>
|
||||
<td style="padding: 5px 0;">{{ $payTrans->transmitted_data['clearing_bankaccountholder'] }}</td>
|
||||
<td style="padding: 5px 0;">{{ $payTrans->transmitted_data['clearing_bankaccountholder'] ?? '—' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 5px 10px 5px 0;"><strong>{{ __('payment.IBAN') }}:</strong></td>
|
||||
<td style="padding: 5px 0; font-family: monospace; letter-spacing: 1px;">{{ $payTrans->transmitted_data['clearing_bankiban'] }}</td>
|
||||
<td style="padding: 5px 0; font-family: monospace; letter-spacing: 1px;">{{ $payTrans->transmitted_data['clearing_bankiban'] ?? '—' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 5px 10px 5px 0;"><strong>{{ __('payment.BIC') }}:</strong></td>
|
||||
<td style="padding: 5px 0; font-family: monospace;">{{ $payTrans->transmitted_data['clearing_bankbic'] }}</td>
|
||||
<td style="padding: 5px 0; font-family: monospace;">{{ $payTrans->transmitted_data['clearing_bankbic'] ?? '—' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 5px 10px 5px 0;"><strong>{{ __('payment.bank') }}:</strong></td>
|
||||
<td style="padding: 5px 0;">{{ $payTrans->transmitted_data['clearing_bankname'] }}</td>
|
||||
<td style="padding: 5px 0;">{{ $payTrans->transmitted_data['clearing_bankname'] ?? '—' }}</td>
|
||||
</tr>
|
||||
<tr style="border-top: 1px solid #ffc107;">
|
||||
<td style="padding: 10px 10px 5px 0;"><strong>{{ __('payment.total_amount') }}:</strong></td>
|
||||
<td style="padding: 10px 0 5px 0; font-size: 18px; font-weight: bold; color: #d35400;">{{ number_format( floatval($payTrans->shopping_payment->amount/100), 2, ",", ".") }} EUR</td>
|
||||
</tr>
|
||||
</table>
|
||||
@else
|
||||
<p class="mb-0 text-muted">{{ __('payment.prepayment_no_clearing_data') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,41 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
<h4>Zahlungen (Stripe / PAYONE)</h4>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-sm-6">
|
||||
<label class="form-label">Aktiver Zahlungsanbieter für <strong>neue</strong> Zahlungen</label>
|
||||
{!! Form::select('settings[payment-active-provider][val]', ['payone' => 'PAYONE', 'stripe' => 'Stripe'], \App\Models\Setting::getContentBySlug('payment-active-provider') ?: 'payone', ['class' => 'form-control']) !!}
|
||||
{{ Form::hidden('settings[payment-active-provider][type]', 'text') }}
|
||||
<small class="text-muted">Wirkt nur auf neue Zahlungsvorgänge — bestehende Zahlungen/Abos behalten ihren Anbieter.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-sm-12">
|
||||
<label class="custom-control custom-checkbox mt-2">
|
||||
{!! Form::checkbox('settings[checkout-maintenance][val]', 1, \App\Models\Setting::getContentBySlug('checkout-maintenance'), ['class'=>'custom-control-input']) !!}
|
||||
<span class="custom-control-label"><strong>Checkout-Wartungsmodus</strong> — alle Besucher sehen „Technische Schwierigkeiten"; nur Admins können bestellen (zum Live-Testen von Stripe)</span>
|
||||
</label>
|
||||
{{ Form::hidden('settings[checkout-maintenance][type]', 'bool') }}
|
||||
|
||||
<label class="custom-control custom-checkbox mt-3">
|
||||
{!! Form::checkbox('settings[abo-payments-frozen][val]', 1, \App\Models\Setting::getContentBySlug('abo-payments-frozen'), ['class'=>'custom-control-input']) !!}
|
||||
<span class="custom-control-label"><strong>Abo-Zahlungen einfrieren</strong> — Abo-Checkout, Retry und Abbuchungs-Cron pausieren mit Hinweis (Not-Schalter bis Stripe-Abos live sind)</span>
|
||||
</label>
|
||||
{{ Form::hidden('settings[abo-payments-frozen][type]', 'bool') }}
|
||||
|
||||
<label class="custom-control custom-checkbox mt-3">
|
||||
{!! Form::checkbox('settings[payment-vorkasse-hidden][val]', 1, \App\Models\Setting::getContentBySlug('payment-vorkasse-hidden'), ['class'=>'custom-control-input']) !!}
|
||||
<span class="custom-control-label"><strong>Vorkasse ausblenden</strong> — Vorkasse läuft über PAYONE und wird im Checkout nicht mehr angeboten</span>
|
||||
</label>
|
||||
{{ Form::hidden('settings[payment-vorkasse-hidden][type]', 'bool') }}
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" name="action" value="save_payment" class="btn btn-primary btn-sm mb-2"><i class="ion ion-ios-save"></i> speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
|
|
|
|||
|
|
@ -133,25 +133,25 @@
|
|||
<strong>{{ __('payment.account_holder') }}:</strong>
|
||||
</td>
|
||||
<td style="padding: 5px 0;">
|
||||
{{ $pay_trans->transmitted_data['clearing_bankaccountholder'] }}</td>
|
||||
{{ $pay_trans->transmitted_data['clearing_bankaccountholder'] ?? '—' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 5px 10px 5px 0;"><strong>{{ __('payment.IBAN') }}:</strong>
|
||||
</td>
|
||||
<td style="padding: 5px 0; font-family: monospace; letter-spacing: 1px;">
|
||||
{{ $pay_trans->transmitted_data['clearing_bankiban'] }}</td>
|
||||
{{ $pay_trans->transmitted_data['clearing_bankiban'] ?? '—' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 5px 10px 5px 0;"><strong>{{ __('payment.BIC') }}:</strong>
|
||||
</td>
|
||||
<td style="padding: 5px 0; font-family: monospace;">
|
||||
{{ $pay_trans->transmitted_data['clearing_bankbic'] }}</td>
|
||||
{{ $pay_trans->transmitted_data['clearing_bankbic'] ?? '—' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 5px 10px 5px 0;"><strong>{{ __('payment.bank') }}:</strong>
|
||||
</td>
|
||||
<td style="padding: 5px 0;">
|
||||
{{ $pay_trans->transmitted_data['clearing_bankname'] }}</td>
|
||||
{{ $pay_trans->transmitted_data['clearing_bankname'] ?? '—' }}</td>
|
||||
</tr>
|
||||
<tr style="border-top: 1px solid #ffc107;">
|
||||
<td style="padding: 10px 10px 5px 0;">
|
||||
|
|
|
|||
|
|
@ -940,7 +940,43 @@
|
|||
<div class="toggle-content">
|
||||
<div class="row nomargin-bottom">
|
||||
|
||||
@if(($shopping_user->abo_options === 1) && (Util::getUserPaymentFor() >= 3))
|
||||
@if(!empty($abo_payments_frozen))
|
||||
<div class="col-lg-12 nomargin clearfix">
|
||||
<div class="alert alert-warning mb-0">
|
||||
<strong>{{ __('payment.abo_payments_frozen_title') }}</strong><br>
|
||||
{{ __('payment.abo_payments_frozen') }}
|
||||
</div>
|
||||
</div>
|
||||
@elseif(!empty($stripe_active))
|
||||
{{-- Stripe Hosted Checkout: Zahlartenwahl passiert bei Stripe --}}
|
||||
<div class="col-lg-12 nomargin clearfix">
|
||||
<label class="mt-0 fs-14 fw-400 radio pull-left nomargin-top" style="width: 100%">
|
||||
{!! Form::radio('payment_method', 'stripe', (!old('payment_method') ? '1' : '')) !!}
|
||||
<i></i> <span class="weight-400"><span class="fa fa-check text-primary"></span> {{ __('payment.stripe_online') }}</span>
|
||||
<img class="float-right" width="90" src="{{asset('images/payments-assets/stripe.png')}}" alt="Stripe">
|
||||
</label>
|
||||
<p class="fs-13 mb-1">{{ __('payment.stripe_online_methods') }}</p>
|
||||
<p class="fs-12 text-muted mb-0">{{ __('payment.stripe_online_copy') }}</p>
|
||||
</div>
|
||||
@if(array_key_exists('VOR', $payment_methods_active) && in_array($payment_methods_active['VOR'], $payment_methods))
|
||||
<div class="col-lg-12 nomargin clearfix">
|
||||
<label class="mt-4 fs-14 fw-400 radio pull-left" style="width: 100%">
|
||||
{!! Form::radio('payment_method', 'vor', '') !!}
|
||||
<i></i> <span class="weight-400"><span class="fa fa-check text-primary"></span> {{ __('payment.prepayment') }}</span>
|
||||
<img class="float-right" width="90" src="{{asset('images/payments-assets/vor.png')}}" alt="Vorkasse">
|
||||
</label>
|
||||
</div>
|
||||
@endif
|
||||
@if(array_key_exists('FNC', $payment_methods_active) && in_array($payment_methods_active['FNC'], $payment_methods))
|
||||
<div class="col-lg-12 nomargin clearfix">
|
||||
<label class="mt-4 fs-14 fw-400 radio pull-left" style="width: 100%">
|
||||
{!! Form::radio('payment_method', 'fnc#MIV', '') !!}
|
||||
<i></i> <span class="weight-400"><span class="fa fa-check text-primary"></span> {{ __('payment.purchase_on_account') }}</span>
|
||||
<img class="float-right" width="90" src="{{asset('images/payments-assets/fnc.png')}}" alt="Rechnungskauf">
|
||||
</label>
|
||||
</div>
|
||||
@endif
|
||||
@elseif(($shopping_user->abo_options === 1) && (Util::getUserPaymentFor() >= 3))
|
||||
@if(array_key_exists('SEPA', $payment_methods_active) && in_array($payment_methods_active['SEPA'], $payment_methods))
|
||||
<div class="col-lg-12 nomargin clearfix">
|
||||
<label class="mt-4 fs-14 fw-400 radio pull-left" style="width: 100%">
|
||||
|
|
@ -1331,8 +1367,12 @@
|
|||
</div>
|
||||
@endif
|
||||
|
||||
@if(!empty($abo_payments_frozen))
|
||||
<div class="alert alert-warning mt-4">{{ __('payment.abo_payments_frozen') }}</div>
|
||||
@else
|
||||
<button type="submit" class="btn btn-primary btn-lg btn-block size-18 btn-text-500 mt-4 faa-parent animated-hover"><i class="fa fa-mail-forward faa-horizontal"></i> {{ __('payment.buy_now') }}</button>
|
||||
<hr><p class="text-center" style="line-height: 1.2em;"><em class="small text-center"> <i class="fa fa-lock"></i> {{ __('payment.buy_now_copy') }}</em></p>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue