Files
marcoandClaude Sonnet 4.6 092dae6085
Deploy to Production (witstore.wende.it) / deploy (push) Successful in 8s
Add coupons, newsletter, country-based shipping, sale prices, USP bar
- Gutscheincodes: DB-Verwaltung (%, €-Rabatt, Mindestbestellwert, Ablauf),
  Eingabefeld im Checkout, Admin unter Marketing → Gutscheine
- Newsletter: Double-Opt-In per E-Mail, Formular im Footer,
  Admin-Liste mit CSV-Export
- Länderzonen-Versand: DE / DACH / EU / WORLD, Versandkosten nach
  Lieferland dynamisch berechnet, Coupon-Rabatt in Bestellung gespeichert
- Sale-Preise: sale_price auf Produkten, durchgestrichener UVP auf
  Produktkarte, Sonderangebote-Sektion auf Startseite
- USP-Leiste auf Startseite: Versand, Zahlung, Rückgabe, Service

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 11:07:14 +02:00

114 lines
4.1 KiB
PHP

<?php
namespace App\Services;
use App\Mail\OrderConfirmationMail;
use App\Mail\OrderShippedMail;
use App\Models\Coupon;
use App\Models\Order;
use App\Models\OrderAddress;
use App\Models\OrderItem;
use App\Models\ProductVariant;
use App\Models\ShippingRate;
use Illuminate\Support\Facades\Mail;
class OrderService
{
public function __construct(private CartService $cart) {}
public function createFromSession(array $addressData, string $paymentMethod): Order
{
$items = $this->cart->get();
$subtotal = $this->cart->subtotal();
$country = $addressData['shipping']['country'] ?? $addressData['billing']['country'] ?? 'DE';
$shippingRate = ShippingRate::forCart($subtotal, $country);
$shippingCost = $shippingRate ? $shippingRate->getCostForTotal($subtotal) : 0;
// Gutschein aus Session
$couponCode = null;
$discountAmount = 0;
if ($code = session('coupon_code')) {
$coupon = Coupon::findValid($code);
if ($coupon) {
$discountAmount = $coupon->calcDiscount($subtotal);
$couponCode = $coupon->code;
$coupon->increment('used_count');
}
}
$taxable = max(0, $subtotal - $discountAmount);
$taxTotal = array_sum(array_map(function ($item) use ($subtotal, $discountAmount) {
$ratio = $subtotal > 0 ? ($item['price'] * $item['qty']) / $subtotal : 0;
$itemNet = ($item['price'] * $item['qty'] - $discountAmount * $ratio) / (1 + $item['tax_rate'] / 100);
return round(($item['price'] * $item['qty'] - $discountAmount * $ratio) - $itemNet, 2);
}, $items));
$total = max(0, $subtotal - $discountAmount) + $shippingCost;
$order = Order::create([
'order_number' => Order::generateOrderNumber(),
'user_id' => auth()->id(),
'guest_email' => auth()->check() ? null : ($addressData['email'] ?? null),
'status' => 'new',
'payment_method' => $paymentMethod,
'coupon_code' => $couponCode,
'discount_amount' => $discountAmount,
'subtotal' => $subtotal,
'tax_total' => $taxTotal,
'shipping_cost' => $shippingCost,
'total' => $total,
]);
foreach ($items as $item) {
OrderItem::create([
'order_id' => $order->id,
'product_variant_id' => $item['variant_id'],
'product_name' => $item['product_name'],
'variant_label' => $item['variant_label'],
'quantity' => $item['qty'],
'unit_price' => $item['price'],
'tax_rate' => $item['tax_rate'],
]);
$variant = ProductVariant::with('product')->find($item['variant_id']);
if ($variant && $variant->product->track_stock) {
$variant->decrement('stock', $item['qty']);
}
}
foreach (['billing', 'shipping'] as $type) {
$src = $addressData[$type] ?? $addressData['billing'];
OrderAddress::create(array_merge($src, ['order_id' => $order->id, 'type' => $type]));
}
$this->cart->clear();
session()->forget('coupon_code');
Mail::to($order->guest_email ?? $order->user?->email)->send(new OrderConfirmationMail($order));
return $order;
}
public function updateStatus(Order $order, string $newStatus, ?string $trackingCode = null): void
{
$order->status = $newStatus;
if ($newStatus === 'payment_received' && !$order->paid_at) {
$order->paid_at = now();
}
if ($trackingCode) {
$order->tracking_code = $trackingCode;
}
$order->save();
if ($newStatus === 'shipped') {
$email = $order->guest_email ?? $order->user?->email;
if ($email) {
Mail::to($email)->send(new OrderShippedMail($order));
}
}
}
}