// /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; 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> CreateOrderAsync(CreateOrderDto orderDto, string? userId) { await using var transaction = await _context.Database.BeginTransactionAsync(); try { // --- 1. Validierung von Kunde, Adressen und Methoden --- var customer = await _customerRepository.GetByUserIdAsync(userId!); if (customer == null) return ServiceResult.Fail(ServiceResultType.Unauthorized, "Kundenprofil nicht gefunden."); 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 ServiceResult.Fail(ServiceResultType.Forbidden, "Ungültige oder nicht zugehörige Liefer- oder Rechnungsadresse."); } var shippingMethod = await _shippingMethodRepository.GetByIdAsync(orderDto.ShippingMethodId); if (shippingMethod == null || !shippingMethod.IsActive) return ServiceResult.Fail(ServiceResultType.InvalidInput, "Ungültige oder inaktive Versandmethode."); var paymentMethod = await _context.PaymentMethods.FindAsync(orderDto.PaymentMethodId); if (paymentMethod == null || !paymentMethod.IsActive) return ServiceResult.Fail(ServiceResultType.InvalidInput, "Ungültige oder inaktive Zahlungsmethode."); // --- 2. Artikel verarbeiten und Lagerbestand prüfen --- var orderItems = new List(); var productIds = orderDto.Items.Select(i => i.ProductId).ToList(); var products = await _context.Products.Where(p => productIds.Contains(p.Id)).ToListAsync(); decimal itemsTotal = 0; foreach (var itemDto in orderDto.Items) { var product = products.FirstOrDefault(p => p.Id == itemDto.ProductId); if (product == null || !product.IsActive) { await transaction.RollbackAsync(); return ServiceResult.Fail(ServiceResultType.Conflict, "Ein Produkt im Warenkorb ist nicht mehr verfügbar."); } if (product.StockQuantity < itemDto.Quantity) { await transaction.RollbackAsync(); return ServiceResult.Fail(ServiceResultType.Conflict, $"Nicht genügend Lagerbestand für '{product.Name}'. Verfügbar: {product.StockQuantity}, benötigt: {itemDto.Quantity}."); } 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 = 0; var discountResult = new DiscountCalculationResult(); if (!string.IsNullOrWhiteSpace(orderDto.CouponCode)) { discountResult = await _discountService.CalculateDiscountAsync(orderItems, orderDto.CouponCode); discountAmount = discountResult.TotalDiscountAmount; if (discountAmount <= 0) { await transaction.RollbackAsync(); return ServiceResult.Fail(ServiceResultType.InvalidInput, "Der angegebene Gutscheincode ist ungültig, abgelaufen oder nicht auf die Produkte im Warenkorb anwendbar."); } if (discountResult.AppliedDiscountIds.Count > 1) { await transaction.RollbackAsync(); return ServiceResult.Fail(ServiceResultType.Failure, "Es können nicht mehrere Rabatte gleichzeitig angewendet werden."); } } decimal shippingCost = shippingMethod.BaseCost; decimal subTotalBeforeDiscount = itemsTotal + shippingCost; if (discountAmount > subTotalBeforeDiscount) { discountAmount = subTotalBeforeDiscount; } decimal subTotalAfterDiscount = subTotalBeforeDiscount - discountAmount; decimal taxRate = await _settingService.GetSettingValueAsync("GlobalTaxRate", 0.19m); decimal taxAmount = subTotalAfterDiscount * taxRate; decimal orderTotal = subTotalAfterDiscount + 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(), 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); // --- 5. Rabattnutzung erhöhen --- if (discountResult.AppliedDiscountIds.Any()) { var appliedDiscounts = await _context.Discounts.Where(d => discountResult.AppliedDiscountIds.Contains(d.Id)).ToListAsync(); foreach (var discount in appliedDiscounts) { discount.CurrentUsageCount++; } } await _context.SaveChangesAsync(); await transaction.CommitAsync(); // --- 6. Erfolgreiche Antwort erstellen --- var createdOrder = await _orderRepository.GetByIdAsync(newOrder.Id); return ServiceResult.Ok(MapToOrderDetailDto(createdOrder!)); } catch (Exception ex) { await transaction.RollbackAsync(); // Log the full exception for debugging purposes // _logger.LogError(ex, "An unexpected error occurred in CreateOrderAsync."); return ServiceResult.Fail(ServiceResultType.Failure, "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut."); } } private OrderDetailDto MapToOrderDetailDto(Order order) { return new OrderDetailDto { Id = order.Id, OrderNumber = order.OrderNumber, OrderDate = order.OrderDate, CustomerId = order.CustomerId, Status = Enum.TryParse(order.OrderStatus, true, out var os) ? os : OrderStatus.Pending, TotalAmount = order.OrderTotal, ShippingAddress = new AddressDto { Id = order.ShippingAddress.Id, FirstName = order.ShippingAddress.FirstName, LastName = order.ShippingAddress.LastName, Street = order.ShippingAddress.Street, HouseNumber = order.ShippingAddress.HouseNumber, City = order.ShippingAddress.City, PostalCode = order.ShippingAddress.PostalCode, Country = order.ShippingAddress.Country, Type = order.ShippingAddress.Type }, BillingAddress = new AddressDto { Id = order.BillingAddress.Id, FirstName = order.BillingAddress.FirstName, LastName = order.BillingAddress.LastName, Street = order.BillingAddress.Street, HouseNumber = order.BillingAddress.HouseNumber, City = order.BillingAddress.City, PostalCode = order.BillingAddress.PostalCode, Country = order.BillingAddress.Country, Type = order.BillingAddress.Type }, PaymentMethod = order.PaymentMethodInfo?.Name, PaymentStatus = Enum.TryParse(order.PaymentStatus, true, out var ps) ? ps : PaymentStatus.Pending, ShippingTrackingNumber = order.ShippingTrackingNumber, ShippedDate = order.ShippedDate, DeliveredDate = order.DeliveredDate, 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() }; } } }