Add PayPal, Stripe, CSV-Export; fix .env.production issue
Deploy to Production (witstore.wende.it) / deploy (push) Successful in 7s

- PayPal: Checkout-Integration via srmklive/paypal, Weiterleitung zu PayPal,
  Bestellung wird nach Capture automatisch auf payment_received gesetzt
- Stripe: Payment Elements im Checkout, Intent-API, Zahlung direkt bestätigt
- CSV-Export: Admin → Bestellungen → CSV exportieren (UTF-8 BOM für Excel)
- PayPal/Stripe nur sichtbar wenn Keys in .env gesetzt
- .env.example um PAYPAL_* und STRIPE_* Keys erweitert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 11:24:02 +02:00
co-authored by Claude Sonnet 4.6
parent b7e4835a0d
commit 248fb21a05
14 changed files with 529 additions and 12 deletions
+101 -7
View File
@@ -1,5 +1,53 @@
@extends('layouts.shop')
@section('title', 'Kasse')
@push('scripts')
@if(config('services.stripe.key'))
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('{{ config('services.stripe.key') }}');
const elements = stripe.elements();
const cardElement = elements.create('card', { style: { base: { fontSize: '16px' } } });
document.addEventListener('DOMContentLoaded', () => {
cardElement.mount('#stripe-card-element');
cardElement.on('change', ({ error }) => {
const el = document.getElementById('stripe-errors');
if (error) { el.textContent = error.message; el.classList.remove('hidden'); }
else { el.classList.add('hidden'); }
});
const form = document.querySelector('form[action="{{ route('checkout.address') }}"]');
form.addEventListener('submit', async (e) => {
const method = document.querySelector('input[name="payment_method"]:checked')?.value;
if (method !== 'stripe') return;
e.preventDefault();
const res = await fetch('{{ route('stripe.intent') }}', {
method: 'POST',
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Content-Type': 'application/json' },
});
const { client_secret } = await res.json();
const { error, paymentIntent } = await stripe.confirmCardPayment(client_secret, {
payment_method: { card: cardElement }
});
if (error) {
document.getElementById('stripe-errors').textContent = error.message;
document.getElementById('stripe-errors').classList.remove('hidden');
return;
}
// Adresse speichern und Stripe-Erfolg-URL aufrufen
const formData = new FormData(form);
formData.set('payment_method', 'stripe');
await fetch('{{ route('checkout.address') }}', { method: 'POST', body: formData });
window.location.href = '{{ route('stripe.success') }}?payment_intent=' + paymentIntent.id;
});
});
</script>
@endif
@endpush
@section('content')
<h1 class="text-2xl font-bold mb-6">Kasse</h1>
@@ -134,23 +182,58 @@
</div>
{{-- Zahlungsart --}}
<div class="bg-white border rounded-lg p-6 mb-6">
<div class="bg-white border rounded-lg p-6 mb-6" x-data="{ method: 'prepayment' }">
<h2 class="font-semibold mb-4">Zahlungsart</h2>
<div class="space-y-3">
<label class="flex items-start gap-3 p-3 border rounded-lg cursor-pointer hover:bg-gray-50">
<input type="radio" name="payment_method" value="prepayment" class="mt-1" checked>
<label class="flex items-start gap-3 p-3 border rounded-lg cursor-pointer hover:bg-gray-50"
:class="method==='prepayment' ? 'border-blue-500 bg-blue-50' : ''">
<input type="radio" name="payment_method" value="prepayment" class="mt-1"
x-model="method" @change="method='prepayment'">
<div>
<div class="font-medium">Vorkasse (Überweisung)</div>
<div class="text-sm text-gray-500">Sie überweisen den Betrag auf unser Bankkonto. Wir versenden nach Zahlungseingang.</div>
</div>
</label>
<label class="flex items-start gap-3 p-3 border rounded-lg cursor-pointer hover:bg-gray-50">
<input type="radio" name="payment_method" value="invoice" class="mt-1">
<label class="flex items-start gap-3 p-3 border rounded-lg cursor-pointer hover:bg-gray-50"
:class="method==='invoice' ? 'border-blue-500 bg-blue-50' : ''">
<input type="radio" name="payment_method" value="invoice" class="mt-1"
x-model="method" @change="method='invoice'">
<div>
<div class="font-medium">Kauf auf Rechnung</div>
<div class="text-sm text-gray-500">Sie erhalten die Rechnung per E-Mail. Zahlungsziel: {{ \App\Models\Setting::get('invoice_payment_days', 14) }} Tage.</div>
</div>
</label>
@if(config('paypal.live.client_id') || config('paypal.sandbox.client_id'))
<label class="flex items-start gap-3 p-3 border rounded-lg cursor-pointer hover:bg-gray-50"
:class="method==='paypal' ? 'border-blue-500 bg-blue-50' : ''">
<input type="radio" name="payment_method" value="paypal" class="mt-1"
x-model="method" @change="method='paypal'">
<div>
<div class="font-medium flex items-center gap-2">
<svg viewBox="0 0 24 24" class="h-5" fill="none"><rect width="24" height="24" rx="4" fill="#003087"/><path d="M8.5 17H6l2-10h4c1.7 0 2.8.9 2.5 2.5-.4 2-2 3-3.8 3H9.2L8.5 17z" fill="#fff"/><path d="M10.5 11.5H12c1.3 0 2.2-.6 2.5-2 .2-1-.3-1.5-1.3-1.5h-1.5l-1.2 3.5z" fill="#009cde"/></svg>
PayPal
</div>
<div class="text-sm text-gray-500">Sofort und sicher mit PayPal bezahlen.</div>
</div>
</label>
@endif
@if(config('services.stripe.key'))
<label class="flex items-start gap-3 p-3 border rounded-lg cursor-pointer hover:bg-gray-50"
:class="method==='stripe' ? 'border-blue-500 bg-blue-50' : ''">
<input type="radio" name="payment_method" value="stripe" class="mt-1"
x-model="method" @change="method='stripe'">
<div>
<div class="font-medium">Kreditkarte (Stripe)</div>
<div class="text-sm text-gray-500">Visa, Mastercard, American Express — verschlüsselt über Stripe.</div>
</div>
</label>
@if(config('services.stripe.key'))
<div x-show="method==='stripe'" class="mt-3 p-3 border rounded-lg bg-gray-50">
<div id="stripe-card-element" class="p-3 bg-white border rounded"></div>
<p id="stripe-errors" class="text-red-500 text-sm mt-2 hidden"></p>
</div>
@endif
@endif
</div>
</div>
@@ -174,9 +257,20 @@
</div>
@endif
<button type="submit" class="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 font-semibold text-lg">
Bestellung prüfen →
<button type="submit" x-show="method!=='paypal'"
class="w-full btn-primary py-3 rounded-lg font-semibold text-lg">
<span x-text="method==='stripe' ? 'Jetzt bezahlen (Kreditkarte)' : 'Bestellung prüfen →'"></span>
</button>
{{-- PayPal: Adresse speichern, dann zu PayPal weiterleiten --}}
<div x-show="method==='paypal'" class="space-y-2">
<input type="hidden" name="payment_method" value="paypal" x-bind:value="method">
<button type="submit" formaction="{{ route('checkout.address') }}"
onclick="this.form.action='{{ route('checkout.address') }}'"
class="w-full bg-yellow-400 hover:bg-yellow-500 text-gray-900 py-3 rounded-lg font-semibold text-lg flex items-center justify-center gap-2">
<svg viewBox="0 0 24 24" class="h-6 w-6"><rect width="24" height="24" rx="4" fill="#003087"/><path d="M8.5 17H6l2-10h4c1.7 0 2.8.9 2.5 2.5-.4 2-2 3-3.8 3H9.2L8.5 17z" fill="#fff"/></svg>
Mit PayPal bezahlen
</button>
</div>
</form>
</div>