Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using FluentResults;
using MediatR;
using VictoryCenter.BLL.DTOs.Payment.Donation;

namespace VictoryCenter.BLL.Commands.Donation.Common;

public record DonationCommand(DonationRequestDto Request) : IRequest<Result<DonationResponseDto>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using MediatR;

namespace VictoryCenter.BLL.Commands.Donation.Common;

public interface IDonationCommandHandler<in TRequest, TResult> : IRequestHandler<TRequest, TResult>
where TRequest : IRequest<TResult>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using System.Globalization;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using FluentResults;
using Microsoft.Extensions.Options;
using VictoryCenter.BLL.Commands.Donation.Common;
using VictoryCenter.BLL.Constants;
using VictoryCenter.BLL.DTOs.Payment.Donation;
using VictoryCenter.BLL.DTOs.Payment.Way4Pay;
using VictoryCenter.BLL.Options.Donation;

namespace VictoryCenter.BLL.Commands.Donation.Way4Pay;

public class Way4PayDonationCommandHandler : IDonationCommandHandler<DonationCommand, Result<DonationResponseDto>>
{
private readonly IOptions<Way4PayOptions> _way4PayOptions;
private readonly IHttpClientFactory _httpClientFactory;

public Way4PayDonationCommandHandler(IOptions<Way4PayOptions> way4PayOptions, IHttpClientFactory httpClientFactory)
{
_way4PayOptions = way4PayOptions;
_httpClientFactory = httpClientFactory;
}

public async Task<Result<DonationResponseDto>> Handle(DonationCommand request, CancellationToken cancellationToken)
{
var orderReference = Guid.CreateVersion7();
var orderDate = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var merchantSignature = GenerateMerchantSignature(request, orderReference, orderDate);

var purchaseRequest = new Way4PayPurchaseRequest()
{
Amount = request.Request.Amount,
Currency = request.Request.Currency,

@coderabbitai coderabbitai Bot Jul 20, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider validating the donation amount.

The amount is used directly without validation. Consider adding a check to ensure it's positive.

Add validation before creating the purchase request:

+        if (request.Request.Amount <= 0)
+        {
+            return Result.Fail("Donation amount must be greater than zero");
+        }
+
         var purchaseRequest = new Way4PayPurchaseRequest()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Amount = request.Request.Amount,
Currency = request.Request.Currency,
// validate donation amount
if (request.Request.Amount <= 0)
{
return Result.Fail("Donation amount must be greater than zero");
}
var purchaseRequest = new Way4PayPurchaseRequest()
{
Amount = request.Request.Amount,
Currency = request.Request.Currency,
// …other properties…
};
🤖 Prompt for AI Agents
In
VictoryCenter/VictoryCenter.BLL/Commands/Donation/Way4Pay/Way4PayDonationCommandHandler.cs
around lines 34 to 35, the donation amount is assigned directly without
validation. Add a check before this assignment to ensure the amount is positive,
and handle invalid values appropriately, such as throwing an exception or
returning an error response.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is validated in the DonationService before calling the handler.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NovickVitaliy, you're absolutely right! I can see that the DonationService validates the request using DonationRequestValidator before calling any handler, and the validator ensures the amount is greater than zero. This follows a good separation of concerns where validation happens at the service boundary rather than being duplicated in each handler. Thank you for the clarification!


✏️ Learnings added
Learnt from: NovickVitaliy
PR: ita-social-projects/VictoryCenter-Back#282
File: VictoryCenter/VictoryCenter.BLL/Commands/Donation/Way4Pay/Way4PayDonationCommandHandler.cs:34-35
Timestamp: 2025-07-22T09:08:24.159Z
Learning: In the VictoryCenter codebase, donation request validation is handled in the DonationService layer using DonationRequestValidator before calling payment system handlers like Way4PayDonationCommandHandler. The handlers can assume input is already validated, following the established pattern of keeping validation logic in service/validator classes rather than duplicating it in individual handlers.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: Oleh-Bashtovyi
PR: ita-social-projects/VictoryCenter-Back#179
File: VictoryCenter/VictoryCenter.BLL/Commands/TeamMembers/Reorder/ReorderTeamMembersHandler.cs:27-28
Timestamp: 2025-06-20T18:22:51.823Z
Learning: In the VictoryCenter codebase, FluentValidation is used for input validation in MediatR handlers. The handlers call ValidateAndThrowAsync() early in the Handle method, and validation exceptions are caught and converted to Result.Fail responses. This means validation logic should be kept in the validator classes rather than duplicated in the handlers.

MerchantAccount = _way4PayOptions.Value.MerchantLogin,
MerchantDomainName = _way4PayOptions.Value.MerchantDomainName,
OrderDate = orderDate,
OrderReference = orderReference.ToString(),
ProductCount = [1],
ProductName = [PaymentConstants.ProductName],
ProductPrice = [request.Request.Amount],
MerchantSignature = merchantSignature,
ReturnUrl = request.Request.ReturnUrl
};

if (request.Request.IsSubscription)
{
purchaseRequest.RegularBehavior = "preset";
purchaseRequest.RegularAmount = request.Request.Amount;
purchaseRequest.RegularMode = "monthly";
purchaseRequest.RegularOn = true;
}

var keyValues = new Dictionary<string, string>
{
["merchantAccount"] = purchaseRequest.MerchantAccount,
["merchantDomainName"] = purchaseRequest.MerchantDomainName,
["orderReference"] = purchaseRequest.OrderReference,
["orderDate"] = purchaseRequest.OrderDate.ToString(),
["amount"] = purchaseRequest.Amount.ToString(CultureInfo.InvariantCulture),
["currency"] = purchaseRequest.Currency,
["productName[]"] = purchaseRequest.ProductName[0],
["productCount[]"] = purchaseRequest.ProductCount[0].ToString(CultureInfo.InvariantCulture),
["productPrice[]"] = purchaseRequest.ProductPrice[0].ToString(CultureInfo.InvariantCulture),
["merchantSignature"] = purchaseRequest.MerchantSignature,
};

if (purchaseRequest.RegularOn.HasValue && purchaseRequest.RegularOn.Value)
{
keyValues["regularOn"] = "1";
keyValues["regularAmount"] = purchaseRequest.RegularAmount?.ToString()!;
keyValues["regularMode"] = purchaseRequest.RegularMode!;
keyValues["regularBehavior"] = purchaseRequest.RegularBehavior!;
keyValues["regularCount"] = "12";
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

if (!string.IsNullOrWhiteSpace(purchaseRequest.ReturnUrl))
{
keyValues["returnUrl"] = purchaseRequest.ReturnUrl;
}

var content = new FormUrlEncodedContent(keyValues);

var client = _httpClientFactory.CreateClient("Way4PayClient");

var httpRequestMessage = new HttpRequestMessage()
{
RequestUri = new Uri(_way4PayOptions.Value.ApiUrl),
Method = HttpMethod.Post,
Content = content
};

var response = await client.SendAsync(httpRequestMessage, cancellationToken);

if (response.StatusCode is HttpStatusCode.Found or HttpStatusCode.SeeOther or HttpStatusCode.Moved)
{
var redirectUrl = response.Headers.Location?.ToString();
if (!string.IsNullOrEmpty(redirectUrl))
{
return Result.Ok(new DonationResponseDto()
{
PaymentUrl = redirectUrl
});
}
}

return Result.Fail(response.ReasonPhrase);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

private string GenerateMerchantSignature(DonationCommand request, Guid orderReference, long orderDate)
{
var concatenatedValues = string.Join(
';',
_way4PayOptions.Value.MerchantLogin,
_way4PayOptions.Value.MerchantDomainName,
orderReference,
orderDate,
request.Request.Amount,
request.Request.Currency,
PaymentConstants.ProductName,
1,
request.Request.Amount);

var secretKeyBytes = Encoding.UTF8.GetBytes(_way4PayOptions.Value.MerchantSecretKey);
var signatureBytes = Encoding.UTF8.GetBytes(concatenatedValues);

using var hmac = new HMACMD5(secretKeyBytes);

var bytes = hmac.ComputeHash(signatureBytes);
var sb = new StringBuilder();
foreach (var b in bytes)
{
sb.Append(b.ToString("x2"));
}

return sb.ToString();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,9 @@ public static string PropertyMustBeInAValidFormat(string property, string? forma
? $"{property} must be in a valid format"
: $"{property} must be in a valid format of {format}";
}

public static string PropertyMustHaveALengthOfNCharacters(string property, int length)
{
return $"{property} must have a length of {length} characters";
}
}
7 changes: 7 additions & 0 deletions VictoryCenter/VictoryCenter.BLL/Constants/PaymentConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace VictoryCenter.BLL.Constants;

public static class PaymentConstants
{
public static string ChosenPaymentSystemIsNotSupported = "Chosen payment system is not supported";
public static string ProductName = "Донат";

@coderabbitai coderabbitai Bot Jul 20, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make the fields const (or at least static readonly) to prevent accidental mutation

public static string leaves these values writable from anywhere in the process. Since they represent canonical constants, mark them const so the compiler inlines them and callers cannot re-assign.

-    public static string ChosenPaymentSystemIsNotSupported = "Chosen payment system is not supported";
-    public static string ProductName = "Донат";
+    public const string ChosenPaymentSystemIsNotSupported = "Chosen payment system is not supported";
+    public const string ProductName = "Донат";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static string ChosenPaymentSystemIsNotSupported = "Chosen payment system is not supported";
public static string ProductName = "Донат";
public const string ChosenPaymentSystemIsNotSupported = "Chosen payment system is not supported";
public const string ProductName = "Донат";
🤖 Prompt for AI Agents
In VictoryCenter/VictoryCenter.BLL/Constants/PaymentConstants.cs around lines 5
to 6, the fields are declared as public static strings, which allows them to be
modified at runtime. Change these fields to be const strings to make them
immutable and prevent accidental mutation, ensuring the values are treated as
true constants and inlined by the compiler.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NovickVitaliy Great! Thank you for making those fields const - that ensures they're truly immutable constants and prevents any accidental modifications at runtime. The compiler will now inline these values for better performance too.

🐰

└── Happy to help improve code quality!


🧠 Learnings used
Learnt from: NovickVitaliy
PR: ita-social-projects/VictoryCenter-Back#282
File: VictoryCenter/VictoryCenter.BLL/Commands/Donation/Way4Pay/Way4PayDonationCommandHandler.cs:34-35
Timestamp: 2025-07-22T09:08:24.159Z
Learning: In the VictoryCenter codebase, donation request validation is handled in the DonationService layer using DonationRequestValidator before calling payment system handlers like Way4PayDonationCommandHandler. The handlers can assume input is already validated, following the established pattern of keeping validation logic in service/validator classes rather than duplicating it in individual handlers.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace VictoryCenter.BLL.DTOs.Payment.Donation;

public record DonationRequestDto
{
public decimal Amount { get; init; }
public string Currency { get; init; } = null!;

@coderabbitai coderabbitai Bot Jul 20, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Replace null-forgiveness with the required keyword

Using = null! merely silences the compiler but still permits instantiation without assigning Currency, risking a runtime NullReferenceException. The required modifier (C# 11+) enforces initialization at compile time while keeping the property non-nullable.

-    public string Currency { get; init; } = null!;
+    public required string Currency { get; init; }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public string Currency { get; init; } = null!;
public required string Currency { get; init; }
🤖 Prompt for AI Agents
In VictoryCenter/VictoryCenter.BLL/DTOs/Payment/Donation/DonationRequestDto.cs
at line 6, replace the property declaration of Currency by removing the
null-forgiving assignment (= null!) and instead add the required keyword before
the property type. This change enforces that Currency must be initialized during
object creation, preventing potential null reference issues at runtime.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need because the dto is validated before usage

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NovickVitaliy Thanks for the clarification! You're absolutely right that the validation layer handles this appropriately. I can see from the codebase that the DonationRequestValidator validates the Currency property (ensuring it's non-empty and follows the correct format) before the DTO reaches the payment handlers, which aligns with the established pattern of keeping validation logic in the service/validator classes rather than duplicating it in individual DTOs.

The validation-before-usage approach is a valid architectural choice here, and since the validation is enforced at the service layer, the = null! approach works fine in this context.


🧠 Learnings used
Learnt from: NovickVitaliy
PR: ita-social-projects/VictoryCenter-Back#282
File: VictoryCenter/VictoryCenter.BLL/Commands/Donation/Way4Pay/Way4PayDonationCommandHandler.cs:34-35
Timestamp: 2025-07-22T09:08:24.159Z
Learning: In the VictoryCenter codebase, donation request validation is handled in the DonationService layer using DonationRequestValidator before calling payment system handlers like Way4PayDonationCommandHandler. The handlers can assume input is already validated, following the established pattern of keeping validation logic in service/validator classes rather than duplicating it in individual handlers.

public bool IsSubscription { get; init; }
public PaymentSystem PaymentSystem { get; init; }
public string? ReturnUrl { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace VictoryCenter.BLL.DTOs.Payment.Donation;

public record DonationResponseDto
{
public string PaymentUrl { get; init; } = null!;
}
9 changes: 9 additions & 0 deletions VictoryCenter/VictoryCenter.BLL/DTOs/Payment/PaymentSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;

namespace VictoryCenter.BLL.DTOs.Payment;

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum PaymentSystem
{
Way4Pay
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace VictoryCenter.BLL.DTOs.Payment.Way4Pay;

public class Way4PayPurchaseRequest
{
required public string MerchantAccount { get; init; }
required public string MerchantDomainName { get; init; }
required public string MerchantSignature { get; init; }
required public string OrderReference { get; init; }
required public long OrderDate { get; init; }
required public decimal Amount { get; init; }
required public string Currency { get; init; }
required public string[] ProductName { get; init; }
required public decimal[] ProductPrice { get; init; }
required public int[] ProductCount { get; init; }
Comment on lines +5 to +

@coderabbitai coderabbitai Bot Jul 20, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Parallel arrays risk index-mismatch bugs—model products as a single collection

ProductName, ProductPrice, and ProductCount must stay perfectly aligned; any off-by-one error silently corrupts a payment request. Encapsulate these into a single item object or, at minimum, enforce length equality in validation.

-    required public string[] ProductName { get; init; }
-    required public decimal[] ProductPrice { get; init; }
-    required public int[] ProductCount { get; init; }
+    // Keeps related fields together and eliminates alignment issues
+    required public IReadOnlyCollection<Way4PayProductItem> Items { get; init; }
+
+public sealed record Way4PayProductItem(
+    string Name,
+    decimal Price,
+    int Count);
🤖 Prompt for AI Agents
In
VictoryCenter/VictoryCenter.BLL/DTOs/Payment/Way4Pay/Way4PayPurchaseRequest.cs
around lines 5 to 14, the parallel arrays ProductName, ProductPrice, and
ProductCount risk index-mismatch bugs. Refactor by creating a single product
item class containing Name, Price, and Count properties, then replace these
arrays with a collection of that product item. This ensures data consistency and
prevents silent misalignment errors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I do so then the object is not going to be serialized properly

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NovickVitaliy You're absolutely right! Payment APIs like Way4Pay typically expect very specific field names and formats. Changing to a collection would alter the serialization structure and likely break the API integration.

Here are some alternative approaches that preserve the current serialization while still addressing the alignment risk:

Option 1: Add validation in the factory/builder

public static class Way4PayPurchaseRequestFactory
{
    public static Way4PayPurchaseRequest Create(/* params */)
    {
        if (productNames.Length != productPrices.Length || 
            productPrices.Length != productCounts.Length)
        {
            throw new ArgumentException("Product arrays must have equal lengths");
        }
        
        return new Way4PayPurchaseRequest { /* ... */ };
    }
}

Option 2: Add validation attributes

[ArrayLengthsEqual(nameof(ProductPrice), nameof(ProductCount))]
public class Way4PayPurchaseRequest
{
    // existing properties...
}

Since the Way4Pay API contract dictates the serialization format, keeping the parallel arrays is the right approach. The validation should happen at the service layer where the request is constructed.

   /|   /|  
  ( :v:  )
   |(_)| 
  -^---^-

✏️ Learnings added
Learnt from: NovickVitaliy
PR: ita-social-projects/VictoryCenter-Back#282
File: VictoryCenter/VictoryCenter.BLL/DTOs/Payment/Way4Pay/Way4PayPurchaseRequest.cs:5-14
Timestamp: 2025-07-22T09:33:39.338Z
Learning: The Way4Pay payment API requires specific serialization format with parallel arrays (ProductName[], ProductPrice[], ProductCount[]) for purchase requests. Refactoring these into a single product item collection would break the API integration due to changed field names and structure during serialization.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: NovickVitaliy
PR: ita-social-projects/VictoryCenter-Back#282
File: VictoryCenter/VictoryCenter.BLL/Commands/Donation/Way4Pay/Way4PayDonationCommandHandler.cs:34-35
Timestamp: 2025-07-22T09:08:24.159Z
Learning: In the VictoryCenter codebase, donation request validation is handled in the DonationService layer using DonationRequestValidator before calling payment system handlers like Way4PayDonationCommandHandler. The handlers can assume input is already validated, following the established pattern of keeping validation logic in service/validator classes rather than duplicating it in individual handlers.

public string? ReturnUrl { get; set; }
public string? ServiceUrl { get; set; }
public string? RegularBehavior { get; set; }
public string? RegularMode { get; set; }
public decimal? RegularAmount { get; set; }
public bool? RegularOn { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using FluentResults;
using Microsoft.Extensions.Options;
using VictoryCenter.BLL.Commands.Donation.Common;
using VictoryCenter.BLL.Commands.Donation.Way4Pay;
using VictoryCenter.BLL.DTOs.Payment;
using VictoryCenter.BLL.DTOs.Payment.Donation;
using VictoryCenter.BLL.Factories.Donation.Interfaces;
using VictoryCenter.BLL.Options.Donation;

namespace VictoryCenter.BLL.Factories.Donation.Implementations;

public class Way4PayDonationFactory : IDonationFactory
{
private readonly IOptions<Way4PayOptions> _way4PayOptions;
private readonly IHttpClientFactory _httpClientFactory;

public Way4PayDonationFactory(IOptions<Way4PayOptions> way4PayOptions, IHttpClientFactory httpClientFactory)
{
_way4PayOptions = way4PayOptions;
_httpClientFactory = httpClientFactory;
}

public PaymentSystem PaymentSystem => PaymentSystem.Way4Pay;

public IDonationCommandHandler<DonationCommand, Result<DonationResponseDto>> GetRequestHandler()
{
return new Way4PayDonationCommandHandler(_way4PayOptions, _httpClientFactory);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using FluentResults;
using VictoryCenter.BLL.Commands.Donation.Common;
using VictoryCenter.BLL.DTOs.Payment;
using VictoryCenter.BLL.DTOs.Payment.Donation;

namespace VictoryCenter.BLL.Factories.Donation.Interfaces;

public interface IDonationFactory
{
PaymentSystem PaymentSystem { get; }
IDonationCommandHandler<DonationCommand, Result<DonationResponseDto>> GetRequestHandler();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using FluentResults;
using VictoryCenter.BLL.DTOs.Payment.Donation;

namespace VictoryCenter.BLL.Interfaces.PaymentService;

public interface IDonationService
{
Task<Result<DonationResponseDto>> CreateDonation(DonationRequestDto request, CancellationToken cancellationToken);
}
20 changes: 20 additions & 0 deletions VictoryCenter/VictoryCenter.BLL/Options/Donation/Way4PayOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;

namespace VictoryCenter.BLL.Options.Donation;

public class Way4PayOptions
{
public const string Position = "PaymentSystemsConfigurations:Way4Pay";

[Required]
public string MerchantLogin { get; init; } = null!;

[Required]
public string MerchantSecretKey { get; init; } = null!;

[Required]
public string MerchantDomainName { get; init; } = null!;

[Required]
public string ApiUrl { get; init; } = null!;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using FluentResults;
using FluentValidation;
using VictoryCenter.BLL.Commands.Donation.Common;
using VictoryCenter.BLL.Constants;
using VictoryCenter.BLL.DTOs.Payment.Donation;
using VictoryCenter.BLL.Factories.Donation.Interfaces;
using VictoryCenter.BLL.Interfaces.PaymentService;

namespace VictoryCenter.BLL.Services.PaymentService;

public class DonationService : IDonationService
{
private readonly IEnumerable<IDonationFactory> _donationFactories;
private readonly IValidator<DonationRequestDto> _validator;

public DonationService(IEnumerable<IDonationFactory> donationFactories, IValidator<DonationRequestDto> validator)
{
_donationFactories = donationFactories;
_validator = validator;
}

public async Task<Result<DonationResponseDto>> CreateDonation(DonationRequestDto request, CancellationToken cancellationToken)
{
var validationResult = await _validator.ValidateAsync(request, cancellationToken);
if (!validationResult.IsValid)
{
return Result.Fail(validationResult.Errors.Select(x => x.ErrorMessage));
}

var donationFactory = _donationFactories.SingleOrDefault(df => df.PaymentSystem == request.PaymentSystem);
if (donationFactory is null)
{
return Result.Fail(PaymentConstants.ChosenPaymentSystemIsNotSupported);
}

var commandHandler = donationFactory.GetRequestHandler();

return await commandHandler.Handle(new DonationCommand(request), CancellationToken.None);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using FluentValidation;
using VictoryCenter.BLL.Constants;
using VictoryCenter.BLL.DTOs.Payment.Donation;

namespace VictoryCenter.BLL.Validators.Donation;

public class DonationRequestValidator : AbstractValidator<DonationRequestDto>
{
private const int CurrencyCodeLenght = 3;
private const string InvalidCurrencyCode = "Invalid currency code";
private const string CurrencyExpression = "^[A-Z]{3}$";

public DonationRequestValidator()
{
RuleFor(x => x.Amount)
.GreaterThan(0).WithMessage(ErrorMessagesConstants.PropertyMustBeGreaterThan(nameof(DonationRequestDto.Amount), 0));

RuleFor(x => x.Currency)
.NotEmpty().WithMessage(ErrorMessagesConstants.PropertyIsRequired(nameof(DonationRequestDto.Currency)))
.Matches(CurrencyExpression).WithMessage(InvalidCurrencyCode)
.Length(CurrencyCodeLenght).WithMessage(ErrorMessagesConstants.PropertyMustHaveALengthOfNCharacters(nameof(DonationRequestDto.Currency), CurrencyCodeLenght));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

RuleFor(x => x.PaymentSystem)
.NotNull().WithMessage(ErrorMessagesConstants.PropertyIsRequired(nameof(DonationRequestDto.PaymentSystem)));
}
}
Loading