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
@@ -0,0 +1,104 @@
<?php
namespace App\Http\Controllers\Shop;
use App\Http\Controllers\Controller;
use App\Services\CartService;
use App\Services\OrderService;
use App\Models\Coupon;
use App\Models\Order;
use App\Models\ShippingRate;
use Illuminate\Http\Request;
use Srmklive\PayPal\Services\PayPal as PayPalClient;
class PayPalController extends Controller
{
public function __construct(private CartService $cart, private OrderService $orderService) {}
public function redirect(Request $request)
{
$checkoutData = session('checkout_data');
if (!$checkoutData || $this->cart->count() === 0) {
return redirect()->route('checkout.index');
}
$subtotal = $this->cart->subtotal();
$country = $checkoutData['shipping']['country'] ?? $checkoutData['billing']['country'] ?? 'DE';
$shippingRate = ShippingRate::forCart($subtotal, $country);
$shippingCost = $shippingRate ? $shippingRate->getCostForTotal($subtotal) : 0;
$discount = 0;
if ($code = session('coupon_code')) {
$coupon = Coupon::findValid($code);
if ($coupon) $discount = $coupon->calcDiscount($subtotal);
}
$total = max(0, $subtotal - $discount) + $shippingCost;
$provider = new PayPalClient;
$provider->setApiCredentials(config('paypal'));
$provider->getAccessToken();
$order = $provider->createOrder([
'intent' => 'CAPTURE',
'purchase_units' => [[
'amount' => [
'currency_code' => 'EUR',
'value' => number_format($total, 2, '.', ''),
],
'description' => config('app.name') . ' Bestellung',
]],
'application_context' => [
'return_url' => route('paypal.success'),
'cancel_url' => route('checkout.confirm'),
],
]);
if (isset($order['id'])) {
session(['paypal_order_id' => $order['id']]);
foreach ($order['links'] as $link) {
if ($link['rel'] === 'approve') {
return redirect($link['href']);
}
}
}
return redirect()->route('checkout.confirm')->with('error', 'PayPal-Verbindung fehlgeschlagen. Bitte versuchen Sie es erneut.');
}
public function success(Request $request)
{
$checkoutData = session('checkout_data');
$paypalOrderId = session('paypal_order_id');
if (!$checkoutData || !$paypalOrderId) {
return redirect()->route('checkout.index');
}
$provider = new PayPalClient;
$provider->setApiCredentials(config('paypal'));
$provider->getAccessToken();
$result = $provider->capturePaymentOrder($paypalOrderId);
if (($result['status'] ?? '') !== 'COMPLETED') {
return redirect()->route('checkout.confirm')->with('error', 'PayPal-Zahlung nicht abgeschlossen.');
}
$checkoutData['payment_method'] = 'paypal';
$order = $this->orderService->createFromSession($checkoutData, 'paypal');
$order->update([
'payment_id' => $paypalOrderId,
'status' => 'payment_received',
'paid_at' => now(),
]);
session()->forget(['checkout_data', 'checkout_country', 'paypal_order_id']);
return redirect()->route('checkout.thankyou', $order->order_number);
}
public function cancel()
{
session()->forget('paypal_order_id');
return redirect()->route('checkout.confirm')->with('error', 'PayPal-Zahlung abgebrochen.');
}
}