diff --git a/Webshop.Api/JsonConverters/PaymentMethodConfigurationConverter.cs b/Webshop.Api/JsonConverters/PaymentMethodConfigurationConverter.cs new file mode 100644 index 0000000..b6c2dc6 --- /dev/null +++ b/Webshop.Api/JsonConverters/PaymentMethodConfigurationConverter.cs @@ -0,0 +1,124 @@ +// src/Webshop.Api/JsonConverters/AdminPaymentMethodDtoConverter.cs +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Webshop.Application.DTOs.Payments; +using Webshop.Domain.Enums; + +namespace Webshop.Api.JsonConverters +{ + public class AdminPaymentMethodDtoConverter : JsonConverter + { + public override AdminPaymentMethodDto Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException("Expected StartObject token."); + } + + // Ein temporäres JsonDocument erstellen, um das Objekt zu analysieren + using var jsonDocument = JsonDocument.ParseValue(ref reader); + var jsonObject = jsonDocument.RootElement; + + var dto = new AdminPaymentMethodDto(); + + // Lese alle Standard-Eigenschaften + if (jsonObject.TryGetProperty(nameof(AdminPaymentMethodDto.Id), out var idElement)) + { + dto.Id = idElement.GetGuid(); + } + + if (jsonObject.TryGetProperty(nameof(AdminPaymentMethodDto.Name), out var nameElement)) + { + dto.Name = nameElement.GetString() ?? string.Empty; + } + + if (jsonObject.TryGetProperty(nameof(AdminPaymentMethodDto.Description), out var descriptionElement)) + { + dto.Description = descriptionElement.GetString(); + } + + if (jsonObject.TryGetProperty(nameof(AdminPaymentMethodDto.IsActive), out var isActiveElement)) + { + dto.IsActive = isActiveElement.GetBoolean(); + } + + if (jsonObject.TryGetProperty(nameof(AdminPaymentMethodDto.ProcessingFee), out var processingFeeElement)) + { + dto.ProcessingFee = processingFeeElement.GetDecimal(); + } + + // Lese den PaymentGatewayType, um zu entscheiden, wie die Konfiguration deserialisiert werden soll + if (!jsonObject.TryGetProperty(nameof(AdminPaymentMethodDto.PaymentGatewayType), out var typeElement) || + !Enum.TryParse(typeElement.GetString(), true, out var gatewayType)) + { + throw new JsonException("PaymentGatewayType is missing or invalid."); + } + dto.PaymentGatewayType = gatewayType; + + // Deserialisiere die Konfiguration basierend auf dem Typ + if (jsonObject.TryGetProperty(nameof(AdminPaymentMethodDto.Configuration), out var configElement) && configElement.ValueKind != JsonValueKind.Null) + { + var configOptions = new JsonSerializerOptions(options); // Klonen der Optionen + configOptions.Converters.Remove(this); // Entferne diesen Konverter, um eine Endlosschleife zu vermeiden + + switch (gatewayType) + { + case PaymentGatewayType.BankTransfer: + dto.Configuration = configElement.Deserialize(configOptions); + break; + case PaymentGatewayType.Stripe: + dto.Configuration = configElement.Deserialize(configOptions); + break; + case PaymentGatewayType.PayPal: + dto.Configuration = configElement.Deserialize(configOptions); + break; + // Fügen Sie hier weitere Fälle hinzu + default: + // Behandeln Sie den Fall, dass keine spezifische Konfiguration benötigt wird + dto.Configuration = null; + break; + } + } + + return dto; + } + + public override void Write(Utf8JsonWriter writer, AdminPaymentMethodDto value, JsonSerializerOptions options) + { + var writeOptions = new JsonSerializerOptions(options); + writeOptions.Converters.Remove(this); // Entferne diesen Konverter, um eine Endlosschleife zu vermeiden + + writer.WriteStartObject(); + + writer.WriteString("id", value.Id); + writer.WriteString("name", value.Name); + writer.WriteString("description", value.Description); + writer.WriteBoolean("isActive", value.IsActive); + // Schreibe den Enum als String + writer.WriteString("paymentGatewayType", value.PaymentGatewayType.ToString()); + + if (value.Configuration != null) + { + writer.WritePropertyName("configuration"); + // Serialisiere das Konfigurationsobjekt mit seinem konkreten Typ + JsonSerializer.Serialize(writer, value.Configuration, value.Configuration.GetType(), writeOptions); + } + else + { + writer.WriteNull("configuration"); + } + + if (value.ProcessingFee.HasValue) + { + writer.WriteNumber("processingFee", value.ProcessingFee.Value); + } + else + { + writer.WriteNull("processingFee"); + } + + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/Webshop.Application/DTOs/Payments/AdminPaymentMethodDto.cs b/Webshop.Application/DTOs/Payments/AdminPaymentMethodDto.cs index ea0d814..e403b25 100644 --- a/Webshop.Application/DTOs/Payments/AdminPaymentMethodDto.cs +++ b/Webshop.Application/DTOs/Payments/AdminPaymentMethodDto.cs @@ -1,6 +1,6 @@ // src/Webshop.Application/DTOs/Payments/AdminPaymentMethodDto.cs -using System; using Webshop.Domain.Enums; +using System.Text.Json.Serialization; // Für JsonConverter namespace Webshop.Application.DTOs.Payments { @@ -11,7 +11,11 @@ namespace Webshop.Application.DTOs.Payments public string? Description { get; set; } public bool IsActive { get; set; } public PaymentGatewayType PaymentGatewayType { get; set; } - public string? Configuration { get; set; } // Als JSON-String, den der Admin bearbeitet + + // Configuration ist jetzt ein Objekt, das je nach PaymentGatewayType + // eine Instanz von BankTransferConfigurationDto, StripeConfigurationDto etc. sein wird. + public object? Configuration { get; set; } + public decimal? ProcessingFee { get; set; } } } \ No newline at end of file diff --git a/Webshop.Application/DTOs/Payments/BankTransferConfigurationDto.cs b/Webshop.Application/DTOs/Payments/BankTransferConfigurationDto.cs new file mode 100644 index 0000000..5366838 --- /dev/null +++ b/Webshop.Application/DTOs/Payments/BankTransferConfigurationDto.cs @@ -0,0 +1,16 @@ +// src/Webshop.Application/DTOs/Payments/BankTransferConfigurationDto.cs +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Application.DTOs.Payments; + +public class BankTransferConfigurationDto : IPaymentMethodConfiguration +{ + [Required] + public string IBAN { get; set; } = string.Empty; + + [Required] + public string BIC { get; set; } = string.Empty; + + [Required] + public string BankName { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Webshop.Application/DTOs/Payments/IPaymentMethodConfiguration.cs b/Webshop.Application/DTOs/Payments/IPaymentMethodConfiguration.cs new file mode 100644 index 0000000..8a0dc7f --- /dev/null +++ b/Webshop.Application/DTOs/Payments/IPaymentMethodConfiguration.cs @@ -0,0 +1,5 @@ +// src/Webshop.Application/DTOs/Payments/IPaymentMethodConfiguration.cs +namespace Webshop.Application.DTOs.Payments; + + // Leeres Marker-Interface für polymorphe Deserialisierung + public interface IPaymentMethodConfiguration { } diff --git a/Webshop.Application/DTOs/Payments/PayPalConfigurationDto.cs b/Webshop.Application/DTOs/Payments/PayPalConfigurationDto.cs new file mode 100644 index 0000000..692b795 --- /dev/null +++ b/Webshop.Application/DTOs/Payments/PayPalConfigurationDto.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Webshop.Application.DTOs.Payments +{ + public class PayPalConfigurationDto : IPaymentMethodConfiguration + { + [Required] + public string ClientId { get; set; } = string.Empty; + + [Required] + public string ClientSecret { get; set; } = string.Empty; + } +} diff --git a/Webshop.Application/DTOs/Payments/StripeConfigurationDto.cs b/Webshop.Application/DTOs/Payments/StripeConfigurationDto.cs new file mode 100644 index 0000000..4c8f490 --- /dev/null +++ b/Webshop.Application/DTOs/Payments/StripeConfigurationDto.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Webshop.Application.DTOs.Payments +{ + public class StripeConfigurationDto : IPaymentMethodConfiguration + { + [Required] + public string PublicKey { get; set; } = string.Empty; + + [Required] + public string SecretKey { get; set; } = string.Empty; + } + +} diff --git a/Webshop.Application/Services/Admin/AdminPaymentMethodService.cs b/Webshop.Application/Services/Admin/AdminPaymentMethodService.cs index fc227f5..6a7aff2 100644 --- a/Webshop.Application/Services/Admin/AdminPaymentMethodService.cs +++ b/Webshop.Application/Services/Admin/AdminPaymentMethodService.cs @@ -1,12 +1,13 @@ // src/Webshop.Application/Services/Admin/AdminPaymentMethodService.cs using System; using System.Collections.Generic; -using System.Linq; // Für Select +using System.Linq; +using System.Text.Json; // Wichtig für JsonSerializer using System.Threading.Tasks; -using Webshop.Application.DTOs.Payments; // AdminPaymentMethodDto +using Webshop.Application.DTOs.Payments; // AdminPaymentMethodDto und die neuen Configuration-DTOs using Webshop.Domain.Entities; // PaymentMethod +using Webshop.Domain.Enums; // PaymentGatewayType using Webshop.Domain.Interfaces; // IPaymentMethodRepository -using Webshop.Domain.Enums; namespace Webshop.Application.Services.Admin { @@ -30,7 +31,8 @@ namespace Webshop.Application.Services.Admin Description = pm.Description, IsActive = pm.IsActive, PaymentGatewayType = pm.PaymentGatewayType, - Configuration = pm.Configuration, // Gibt den JSON-String für den Admin zurück + // Deserialisiere den JSON-String aus der DB in ein Objekt für die Admin-Ansicht + Configuration = pm.Configuration != null ? JsonSerializer.Deserialize(pm.Configuration) : null, ProcessingFee = pm.ProcessingFee }).ToList(); } @@ -47,27 +49,36 @@ namespace Webshop.Application.Services.Admin Description = paymentMethod.Description, IsActive = paymentMethod.IsActive, PaymentGatewayType = paymentMethod.PaymentGatewayType, - Configuration = paymentMethod.Configuration, + // Deserialisiere den JSON-String aus der DB in ein Objekt + Configuration = paymentMethod.Configuration != null ? JsonSerializer.Deserialize(paymentMethod.Configuration) : null, ProcessingFee = paymentMethod.ProcessingFee }; } public async Task CreateAsync(AdminPaymentMethodDto paymentMethodDto) { + var (isValid, configJson, errorMessage) = ValidateAndSerializeConfiguration(paymentMethodDto.PaymentGatewayType, paymentMethodDto.Configuration); + if (!isValid) + { + // In einer echten Anwendung würden Sie hier eine benutzerdefinierte Validierungs-Exception werfen, + // damit der Controller einen 400 Bad Request zurückgeben kann. + throw new ArgumentException(errorMessage); + } + var newPaymentMethod = new PaymentMethod { - Id = Guid.NewGuid(), // API generiert die ID + Id = Guid.NewGuid(), Name = paymentMethodDto.Name, Description = paymentMethodDto.Description, IsActive = paymentMethodDto.IsActive, PaymentGatewayType = paymentMethodDto.PaymentGatewayType, - Configuration = paymentMethodDto.Configuration, + Configuration = configJson, // Speichere den validierten JSON-String ProcessingFee = paymentMethodDto.ProcessingFee }; await _paymentMethodRepository.AddAsync(newPaymentMethod); - paymentMethodDto.Id = newPaymentMethod.Id; // ID zurückschreiben + paymentMethodDto.Id = newPaymentMethod.Id; return paymentMethodDto; } @@ -76,12 +87,17 @@ namespace Webshop.Application.Services.Admin var existingPaymentMethod = await _paymentMethodRepository.GetByIdAsync(paymentMethodDto.Id); if (existingPaymentMethod == null) return false; - // Eigenschaften aktualisieren + var (isValid, configJson, errorMessage) = ValidateAndSerializeConfiguration(paymentMethodDto.PaymentGatewayType, paymentMethodDto.Configuration); + if (!isValid) + { + throw new ArgumentException(errorMessage); + } + existingPaymentMethod.Name = paymentMethodDto.Name; existingPaymentMethod.Description = paymentMethodDto.Description; existingPaymentMethod.IsActive = paymentMethodDto.IsActive; existingPaymentMethod.PaymentGatewayType = paymentMethodDto.PaymentGatewayType; - existingPaymentMethod.Configuration = paymentMethodDto.Configuration; + existingPaymentMethod.Configuration = configJson; existingPaymentMethod.ProcessingFee = paymentMethodDto.ProcessingFee; await _paymentMethodRepository.UpdateAsync(existingPaymentMethod); @@ -96,5 +112,51 @@ namespace Webshop.Application.Services.Admin await _paymentMethodRepository.DeleteAsync(id); return true; } + + private (bool IsValid, string? ConfigJson, string? ErrorMessage) ValidateAndSerializeConfiguration(PaymentGatewayType type, object? configObject) + { + if (configObject == null) return (true, null, null); + + var configElement = (JsonElement)configObject; + + try + { + switch (type) + { + case PaymentGatewayType.BankTransfer: + var bankConfig = configElement.Deserialize(); + if (bankConfig == null || string.IsNullOrEmpty(bankConfig.IBAN) || string.IsNullOrEmpty(bankConfig.BIC)) + { + return (false, null, "Für Banküberweisung müssen IBAN und BIC angegeben werden."); + } + return (true, JsonSerializer.Serialize(bankConfig), null); + + case PaymentGatewayType.Stripe: + var stripeConfig = configElement.Deserialize(); + if (stripeConfig == null || string.IsNullOrEmpty(stripeConfig.PublicKey) || string.IsNullOrEmpty(stripeConfig.SecretKey)) + { + return (false, null, "Für Stripe müssen PublicKey und SecretKey angegeben werden."); + } + return (true, JsonSerializer.Serialize(stripeConfig), null); + + case PaymentGatewayType.PayPal: + var payPalConfig = configElement.Deserialize(); + if (payPalConfig == null || string.IsNullOrEmpty(payPalConfig.ClientId) || string.IsNullOrEmpty(payPalConfig.ClientSecret)) + { + return (false, null, "Für PayPal müssen ClientId und ClientSecret angegeben werden."); + } + return (true, JsonSerializer.Serialize(payPalConfig), null); + + default: + // Für Typen wie Invoice oder CashOnDelivery, die keine spezielle Konfiguration brauchen, + // ist ein leeres JSON-Objekt '{}' ein gültiger Wert. + return (true, JsonSerializer.Serialize(new object()), null); + } + } + catch (JsonException ex) + { + return (false, null, $"Ungültiges JSON-Format für die Konfiguration: {ex.Message}"); + } + } } } \ No newline at end of file