// src/Webshop.Application/Services/Customers/CheckoutService.cs using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Webshop.Application.DTOs.Customers; using Webshop.Application.DTOs.Orders; using Webshop.Application.Services.Customers.Interfaces; using Webshop.Application.Services.Public.Interfaces; using Webshop.Domain.Entities; using Webshop.Domain.Enums; using Webshop.Domain.Interfaces; using Webshop.Infrastructure.Data; namespace Webshop.Application.Services.Customers { public class CheckoutService : ICheckoutService { private readonly ApplicationDbContext _context; private readonly IOrderRepository _orderRepository; private readonly ICustomerRepository _customerRepository; private readonly IShippingMethodRepository _shippingMethodRepository; private readonly ISettingService _settingService; private readonly IDiscountService _discountService; public CheckoutService( ApplicationDbContext context, IOrderRepository orderRepository, ICustomerRepository customerRepository, IShippingMethodRepository shippingMethodRepository, ISettingService settingService, IDiscountService discountService) { _context = context; _orderRepository = orderRepository; _customerRepository = customerRepository; _shippingMethodRepository = shippingMethodRepository; _settingService = settingService; _discountService = discountService; } public async Task<(bool Success, OrderDetailDto? CreatedOrder, string? ErrorMessage)> CreateOrderAsync(CreateOrderDto orderDto, string userId) { // Startet eine Datenbank-Transaktion. Entweder alles klappt, oder alles wird zurückgerollt. await using var transaction = await _context.Database.BeginTransactionAsync(); try { // --- 1. Validierung der Eingabedaten --- var customer = await _customerRepository.GetByUserIdAsync(userId); if (customer == null) return (false, null, "Kundenprofil nicht gefunden."); var shippingMethod = await _shippingMethodRepository.GetByIdAsync(orderDto.ShippingMethodId); if (shippingMethod == null || !shippingMethod.IsActive) return (false, null, "Ungültige oder inaktive Versandmethode."); var paymentMethod = await _context.PaymentMethods.FindAsync(orderDto.PaymentMethodId); if (paymentMethod == null || !paymentMethod.IsActive) return (false, null, "Ungültige oder inaktive Zahlungsmethode."); if (!await _context.Addresses.AnyAsync(a => a.Id == orderDto.ShippingAddressId && a.CustomerId == customer.Id) || !await _context.Addresses.AnyAsync(a => a.Id == orderDto.BillingAddressId && a.CustomerId == customer.Id)) { return (false, null, "Ungültige oder nicht zugehörige Liefer- oder Rechnungsadresse."); } // --- 2. Artikel verarbeiten und Lagerbestand prüfen --- var orderItems = new List(); decimal itemsTotal = 0; foreach (var itemDto in orderDto.Items) { var product = await _context.Products.FindAsync(itemDto.ProductId); if (product == null || !product.IsActive) { await transaction.RollbackAsync(); return (false, null, $"Produkt mit ID {itemDto.ProductId} ist nicht verfügbar."); } if (product.StockQuantity < itemDto.Quantity) { await transaction.RollbackAsync(); return (false, null, $"Nicht genügend Lagerbestand für '{product.Name}'. Verfügbar: {product.StockQuantity}."); } // Lagerbestand reduzieren product.StockQuantity -= itemDto.Quantity; var orderItem = new OrderItem { ProductId = product.Id, ProductVariantId = itemDto.ProductVariantId, ProductName = product.Name, ProductSKU = product.SKU, Quantity = itemDto.Quantity, UnitPrice = product.Price, TotalPrice = product.Price * itemDto.Quantity }; orderItems.Add(orderItem); itemsTotal += orderItem.TotalPrice; } // --- 3. Preise, Rabatte, Steuern und Gesamtbetrag berechnen --- decimal discountAmount = await _discountService.CalculateDiscountAsync(orderItems, orderDto.CouponCode); decimal shippingCost = shippingMethod.BaseCost; decimal subTotal = itemsTotal + shippingCost - discountAmount; if (subTotal < 0) subTotal = 0; decimal taxRate = await _settingService.GetSettingValueAsync("GlobalTaxRate", 0.19m); decimal taxAmount = subTotal * taxRate; decimal orderTotal = subTotal + taxAmount; // --- 4. Bestellung erstellen --- var newOrder = new Order { CustomerId = customer.Id, OrderNumber = $"WS-{DateTime.UtcNow:yyyyMMdd}-{new Random().Next(1000, 9999)}", OrderDate = DateTimeOffset.UtcNow, OrderStatus = OrderStatus.Pending.ToString(), PaymentStatus = PaymentStatus.Pending.ToString(), // Wird später vom Zahlungsanbieter aktualisiert OrderTotal = orderTotal, ShippingCost = shippingCost, TaxAmount = taxAmount, DiscountAmount = discountAmount, PaymentMethod = paymentMethod.Name, PaymentMethodId = paymentMethod.Id, ShippingMethodId = shippingMethod.Id, BillingAddressId = orderDto.BillingAddressId, ShippingAddressId = orderDto.ShippingAddressId, OrderItems = orderItems }; await _orderRepository.AddAsync(newOrder); // Speichert die Bestellung und die Artikel await _context.SaveChangesAsync(); // Speichert die Lagerbestandsänderungen await transaction.CommitAsync(); // Transaktion erfolgreich abschließen // --- 5. Erfolgreiche Antwort erstellen --- var createdOrder = await _orderRepository.GetByIdAsync(newOrder.Id); // Lade die erstellte Bestellung mit allen Details var orderDetailDto = MapToOrderDetailDto(createdOrder!); // Mapping zum DTO return (true, orderDetailDto, null); } catch (Exception ex) { await transaction.RollbackAsync(); // Hier Fehler loggen return (false, null, $"Ein unerwarteter Fehler ist aufgetreten: {ex.Message}"); } } // Helper-Methode für das Mapping, um Code-Duplizierung zu vermeiden private OrderDetailDto MapToOrderDetailDto(Order order) { return new OrderDetailDto { Id = order.Id, OrderNumber = order.OrderNumber, OrderDate = order.OrderDate, CustomerId = order.CustomerId, Status = Enum.Parse(order.OrderStatus), TotalAmount = order.OrderTotal, ShippingAddress = new AddressDto { /* Mapping */ }, BillingAddress = new AddressDto { /* Mapping */ }, PaymentMethod = order.PaymentMethod, PaymentStatus = Enum.Parse(order.PaymentStatus), OrderItems = order.OrderItems.Select(oi => new OrderItemDto { Id = oi.Id, ProductId = oi.ProductId, ProductVariantId = oi.ProductVariantId, ProductName = oi.ProductName, ProductSKU = oi.ProductSKU, Quantity = oi.Quantity, UnitPrice = oi.UnitPrice, TotalPrice = oi.TotalPrice }).ToList() }; } } }