feat: Implement Public Message Management Commands and Queries
- Add GetUserPackageStatusQueryValidator for user package status validation. - Create ArchiveMessageCommand and ArchiveMessageCommandHandler for archiving public messages. - Implement ArchiveMessageCommandValidator to validate message ID. - Introduce PublishMessageCommand and PublishMessageCommandHandler for publishing messages. - Add PublishMessageCommandValidator for validating publish message requests. - Implement GetPublicMessageQuery and GetPublicMessageQueryHandler for retrieving public messages. - Create GetPublicMessageQueryValidator for validating public message requests. - Add ApplyDiscountToOrderCommand and ApplyDiscountToOrderCommandHandler for applying discounts to orders. - Implement ApplyDiscountToOrderCommandValidator for validating discount application requests. - Create UpdateOrderStatusCommand and UpdateOrderStatusCommandHandler for changing order statuses. - Implement UpdateOrderStatusCommandValidator for validating order status updates. - Add CalculateOrderPVQuery and CalculateOrderPVQueryHandler for calculating order PV. - Implement CalculateOrderPVQueryValidator for validating PV calculation requests. - Create GetOrdersByDateRangeQuery and GetOrdersByDateRangeQueryHandler for retrieving orders by date range. - Implement GetOrdersByDateRangeQueryValidator for validating date range queries. - Add PublicMessage entity to represent public messages in the system. - Implement PublicMessageService for handling public message operations via gRPC.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
||||
|
||||
/// <summary>
|
||||
/// خرید پکیج طلایی (شروع فرآیند پرداخت)
|
||||
/// </summary>
|
||||
public record PurchaseGoldenPackageCommand : IRequest<PurchaseGoldenPackageResponseDto>
|
||||
{
|
||||
public long UserId { get; init; }
|
||||
public long PackageId { get; init; }
|
||||
public string ReturnUrl { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
public class PurchaseGoldenPackageResponseDto
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public long OrderId { get; set; }
|
||||
public string PaymentGatewayUrl { get; set; } = string.Empty;
|
||||
public string TrackingCode { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
||||
|
||||
public class PurchaseGoldenPackageCommandHandler : IRequestHandler<PurchaseGoldenPackageCommand, PurchaseGoldenPackageResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly ILogger<PurchaseGoldenPackageCommandHandler> _logger;
|
||||
|
||||
public PurchaseGoldenPackageCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
ILogger<PurchaseGoldenPackageCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_paymentGateway = paymentGateway;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<PurchaseGoldenPackageResponseDto> Handle(PurchaseGoldenPackageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: پیادهسازی خرید پکیج طلایی
|
||||
//
|
||||
// 1. پیدا کردن کاربر و بررسی شرایط:
|
||||
// - var user = await _context.Users
|
||||
// .Include(u => u.UserOrders)
|
||||
// .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken)
|
||||
// - if (user == null) throw new NotFoundException("کاربر یافت نشد")
|
||||
// - if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
|
||||
// throw new InvalidOperationException("شما قبلاً پکیج طلایی خریداری کردهاید")
|
||||
//
|
||||
// 2. پیدا کردن پکیج:
|
||||
// - var package = await _context.Packages
|
||||
// .FirstOrDefaultAsync(p => p.Id == request.PackageId && p.IsAvailable, cancellationToken)
|
||||
// - if (package == null) throw new NotFoundException("پکیج یافت نشد")
|
||||
// - if (package.Name != "طلایی")
|
||||
// throw new InvalidOperationException("فقط پکیج طلایی قابل خرید است")
|
||||
//
|
||||
// 3. ایجاد سفارش:
|
||||
// - var order = new UserOrder {
|
||||
// UserId = user.Id,
|
||||
// OrderNumber = GenerateOrderNumber(), // مثلاً "ORD" + DateTime.UtcNow.Ticks
|
||||
// TotalPrice = package.Price, // 56,000,000
|
||||
// Status = OrderStatus.Pending,
|
||||
// PaymentMethod = PaymentMethod.IPG,
|
||||
// OrderType = OrderType.PackagePurchase, // enum جدید
|
||||
// PackageId = package.Id
|
||||
// }
|
||||
// - _context.UserOrders.Add(order)
|
||||
// - await _context.SaveChangesAsync(cancellationToken)
|
||||
//
|
||||
// 4. شروع پرداخت با درگاه:
|
||||
// - var paymentResult = await _paymentGateway.InitiatePaymentAsync(
|
||||
// orderId: order.Id,
|
||||
// amount: order.TotalPrice,
|
||||
// description: $"خرید پکیج {package.Name}",
|
||||
// returnUrl: request.ReturnUrl,
|
||||
// cancellationToken: cancellationToken
|
||||
// )
|
||||
// - if (!paymentResult.Success)
|
||||
// throw new InvalidOperationException($"خطا در اتصال به درگاه: {paymentResult.ErrorMessage}")
|
||||
//
|
||||
// 5. ذخیره اطلاعات پرداخت:
|
||||
// - order.TrackingCode = paymentResult.TrackingCode
|
||||
// - order.PaymentGatewayToken = paymentResult.Token
|
||||
// - await _context.SaveChangesAsync(cancellationToken)
|
||||
//
|
||||
// 6. برگشت نتیجه:
|
||||
// - _logger.LogInformation("Golden package purchase initiated for user {UserId}, order {OrderId}", user.Id, order.Id)
|
||||
// - return new PurchaseGoldenPackageResponseDto {
|
||||
// Success = true,
|
||||
// Message = "لطفاً به درگاه پرداخت منتقل شوید",
|
||||
// OrderId = order.Id,
|
||||
// PaymentGatewayUrl = paymentResult.PaymentUrl,
|
||||
// TrackingCode = paymentResult.TrackingCode
|
||||
// }
|
||||
//
|
||||
// نکته 1: OrderType.PackagePurchase را به OrderType enum اضافه کنید
|
||||
// نکته 2: PackageId nullable است در UserOrder - مطمئن شوید میتوانید set کنید
|
||||
// نکته 3: کاربر به صفحه paymentResult.PaymentUrl redirect میشود
|
||||
// نکته 4: پس از پرداخت موفق، VerifyGoldenPackagePurchase فراخوانی میشود
|
||||
|
||||
throw new NotImplementedException("PurchaseGoldenPackage needs implementation");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
||||
|
||||
public class PurchaseGoldenPackageCommandValidator : AbstractValidator<PurchaseGoldenPackageCommand>
|
||||
{
|
||||
public PurchaseGoldenPackageCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر باید بزرگتر از 0 باشد");
|
||||
|
||||
RuleFor(x => x.PackageId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه پکیج باید بزرگتر از 0 باشد");
|
||||
|
||||
RuleFor(x => x.ReturnUrl)
|
||||
.NotEmpty()
|
||||
.WithMessage("آدرس بازگشت الزامی است")
|
||||
.Must(url => Uri.TryCreate(url, UriKind.Absolute, out _))
|
||||
.WithMessage("آدرس بازگشت معتبر نیست");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
||||
|
||||
/// <summary>
|
||||
/// تایید پرداخت پکیج طلایی (پس از بازگشت از درگاه)
|
||||
/// </summary>
|
||||
public record VerifyGoldenPackagePurchaseCommand : IRequest<VerifyGoldenPackagePurchaseResponseDto>
|
||||
{
|
||||
public long OrderId { get; init; }
|
||||
public string Authority { get; init; } = string.Empty;
|
||||
public string Status { get; init; } = string.Empty; // OK یا NOK
|
||||
}
|
||||
|
||||
public class VerifyGoldenPackagePurchaseResponseDto
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public long OrderId { get; set; }
|
||||
public long TransactionId { get; set; }
|
||||
public string ReferenceCode { get; set; } = string.Empty;
|
||||
public long WalletBalance { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
||||
|
||||
public class VerifyGoldenPackagePurchaseCommandHandler : IRequestHandler<VerifyGoldenPackagePurchaseCommand, VerifyGoldenPackagePurchaseResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly ILogger<VerifyGoldenPackagePurchaseCommandHandler> _logger;
|
||||
|
||||
public VerifyGoldenPackagePurchaseCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
ILogger<VerifyGoldenPackagePurchaseCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_paymentGateway = paymentGateway;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<VerifyGoldenPackagePurchaseResponseDto> Handle(VerifyGoldenPackagePurchaseCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: پیادهسازی تایید پرداخت پکیج طلایی
|
||||
//
|
||||
// 1. بررسی Status از درگاه:
|
||||
// - if (request.Status != "OK")
|
||||
// throw new InvalidOperationException("پرداخت توسط کاربر لغو شد")
|
||||
//
|
||||
// 2. پیدا کردن سفارش:
|
||||
// - var order = await _context.UserOrders
|
||||
// .Include(o => o.User)
|
||||
// .ThenInclude(u => u.UserWallet)
|
||||
// .FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken)
|
||||
// - if (order == null) throw new NotFoundException("سفارش یافت نشد")
|
||||
// - if (order.Status != OrderStatus.Pending)
|
||||
// throw new InvalidOperationException("این سفارش قبلاً پردازش شده است")
|
||||
//
|
||||
// 3. Verify با درگاه پرداخت:
|
||||
// - var verifyResult = await _paymentGateway.VerifyPaymentAsync(
|
||||
// authority: request.Authority,
|
||||
// amount: order.TotalPrice,
|
||||
// cancellationToken: cancellationToken
|
||||
// )
|
||||
// - if (!verifyResult.Success) {
|
||||
// order.Status = OrderStatus.PaymentFailed
|
||||
// order.PaymentFailureReason = verifyResult.ErrorMessage
|
||||
// await _context.SaveChangesAsync(cancellationToken)
|
||||
// throw new InvalidOperationException($"تایید پرداخت ناموفق: {verifyResult.ErrorMessage}")
|
||||
// }
|
||||
//
|
||||
// 4. شارژ کیف پول:
|
||||
// - var wallet = order.User.UserWallet
|
||||
// - if (wallet == null) {
|
||||
// wallet = new UserWallet { UserId = order.UserId, Balance = 0, DiscountBalance = 0 }
|
||||
// _context.UserWallets.Add(wallet)
|
||||
// }
|
||||
// - wallet.Balance += order.TotalPrice // اضافه شدن 56,000,000
|
||||
//
|
||||
// 5. ثبت Transaction:
|
||||
// - var transaction = new Transaction {
|
||||
// UserId = order.UserId,
|
||||
// Amount = order.TotalPrice,
|
||||
// Type = TransactionType.DepositIpg,
|
||||
// Status = TransactionStatus.Completed,
|
||||
// Description = $"شارژ کیف پول از خرید پکیج طلایی - سفارش {order.OrderNumber}",
|
||||
// ReferenceCode = verifyResult.ReferenceCode,
|
||||
// OrderId = order.Id
|
||||
// }
|
||||
// - _context.Transactions.Add(transaction)
|
||||
//
|
||||
// 6. ثبت UserWalletChangeLog:
|
||||
// - var changeLog = new UserWalletChangeLog {
|
||||
// UserId = order.UserId,
|
||||
// ChangeValue = order.TotalPrice,
|
||||
// ChangeType = ChangeType.Deposit,
|
||||
// CurrentBalance = wallet.Balance,
|
||||
// Description = $"شارژ از خرید پکیج طلایی",
|
||||
// TransactionId = transaction.Id
|
||||
// }
|
||||
// - _context.UserWalletChangeLogs.Add(changeLog)
|
||||
//
|
||||
// 7. Set PackagePurchaseMethod:
|
||||
// - order.User.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase
|
||||
//
|
||||
// 8. بهروزرسانی سفارش:
|
||||
// - order.Status = OrderStatus.Processing // یا Completed
|
||||
// - order.PaymentStatus = PaymentStatus.Paid
|
||||
// - order.PaidAt = DateTime.UtcNow
|
||||
// - order.BankReferenceId = verifyResult.ReferenceCode
|
||||
//
|
||||
// 9. ذخیره همه تغییرات:
|
||||
// - await _context.SaveChangesAsync(cancellationToken)
|
||||
//
|
||||
// 10. Log و برگشت:
|
||||
// - _logger.LogInformation("Golden package verified for user {UserId}, order {OrderId}, wallet charged {Amount}",
|
||||
// order.UserId, order.Id, order.TotalPrice)
|
||||
// - return new VerifyGoldenPackagePurchaseResponseDto {
|
||||
// Success = true,
|
||||
// Message = "پرداخت با موفقیت تایید شد. کیف پول شما شارژ گردید",
|
||||
// OrderId = order.Id,
|
||||
// TransactionId = transaction.Id,
|
||||
// ReferenceCode = verifyResult.ReferenceCode,
|
||||
// WalletBalance = wallet.Balance
|
||||
// }
|
||||
//
|
||||
// نکته 1: کاربر هنوز عضو باشگاه نشده - باید ActivateClubMembership صدا بزند
|
||||
// نکته 2: TransactionType.DepositIpg را بررسی کنید موجود باشد
|
||||
// نکته 3: در صورت خطا، سفارش به PaymentFailed تغییر وضعیت میدهد
|
||||
// نکته 4: این فرآیند idempotent نیست - باید بررسی شود سفارش Pending باشد
|
||||
|
||||
throw new NotImplementedException("VerifyGoldenPackagePurchase needs implementation");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
||||
|
||||
public class VerifyGoldenPackagePurchaseCommandValidator : AbstractValidator<VerifyGoldenPackagePurchaseCommand>
|
||||
{
|
||||
public VerifyGoldenPackagePurchaseCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.OrderId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه سفارش باید بزرگتر از 0 باشد");
|
||||
|
||||
RuleFor(x => x.Authority)
|
||||
.NotEmpty()
|
||||
.WithMessage("کد Authority الزامی است");
|
||||
|
||||
RuleFor(x => x.Status)
|
||||
.NotEmpty()
|
||||
.WithMessage("وضعیت پرداخت الزامی است")
|
||||
.Must(s => s == "OK" || s == "NOK")
|
||||
.WithMessage("وضعیت باید OK یا NOK باشد");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus;
|
||||
|
||||
/// <summary>
|
||||
/// دریافت وضعیت خرید پکیج کاربر
|
||||
/// </summary>
|
||||
public record GetUserPackageStatusQuery : IRequest<UserPackageStatusDto>
|
||||
{
|
||||
public long UserId { get; init; }
|
||||
}
|
||||
|
||||
public class UserPackageStatusDto
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
public string PackagePurchaseMethod { get; set; } = string.Empty; // None, DayaLoan, DirectPurchase
|
||||
public bool HasPurchasedPackage { get; set; }
|
||||
public bool IsClubMemberActive { get; set; }
|
||||
public long WalletBalance { get; set; }
|
||||
public long DiscountBalance { get; set; }
|
||||
public bool CanActivateClubMembership { get; set; }
|
||||
public string? LastOrderNumber { get; set; }
|
||||
public DateTime? LastPurchaseDate { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus;
|
||||
|
||||
public class GetUserPackageStatusQueryHandler : IRequestHandler<GetUserPackageStatusQuery, UserPackageStatusDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetUserPackageStatusQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<UserPackageStatusDto> Handle(GetUserPackageStatusQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: پیادهسازی دریافت وضعیت پکیج کاربر
|
||||
//
|
||||
// 1. دریافت اطلاعات کاربر:
|
||||
// - var user = await _context.Users
|
||||
// .Include(u => u.UserWallet)
|
||||
// .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken)
|
||||
// - if (user == null) throw new NotFoundException("کاربر یافت نشد")
|
||||
//
|
||||
// 2. دریافت عضویت باشگاه:
|
||||
// - var clubMembership = await _context.ClubMemberships
|
||||
// .FirstOrDefaultAsync(c => c.UserId == user.Id && c.IsActive, cancellationToken)
|
||||
//
|
||||
// 3. دریافت آخرین سفارش پکیج:
|
||||
// - var lastPackageOrder = await _context.UserOrders
|
||||
// .Where(o => o.UserId == user.Id && o.PackageId != null)
|
||||
// .OrderByDescending(o => o.Created)
|
||||
// .FirstOrDefaultAsync(cancellationToken)
|
||||
//
|
||||
// 4. بررسی شرایط فعالسازی باشگاه:
|
||||
// - var wallet = user.UserWallet
|
||||
// - bool canActivate =
|
||||
// user.PackagePurchaseMethod != PackagePurchaseMethod.None &&
|
||||
// clubMembership == null &&
|
||||
// wallet != null &&
|
||||
// wallet.Balance >= 56_000_000
|
||||
//
|
||||
// 5. برگشت DTO:
|
||||
// - return new UserPackageStatusDto {
|
||||
// UserId = user.Id,
|
||||
// PackagePurchaseMethod = user.PackagePurchaseMethod.ToString(),
|
||||
// HasPurchasedPackage = user.PackagePurchaseMethod != PackagePurchaseMethod.None,
|
||||
// IsClubMemberActive = clubMembership != null,
|
||||
// WalletBalance = wallet?.Balance ?? 0,
|
||||
// DiscountBalance = wallet?.DiscountBalance ?? 0,
|
||||
// CanActivateClubMembership = canActivate,
|
||||
// LastOrderNumber = lastPackageOrder?.OrderNumber,
|
||||
// LastPurchaseDate = lastPackageOrder?.Created
|
||||
// }
|
||||
//
|
||||
// نکته: این query برای UI مفید است تا وضعیت کاربر را نمایش دهد
|
||||
|
||||
throw new NotImplementedException("GetUserPackageStatus needs implementation");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus;
|
||||
|
||||
public class GetUserPackageStatusQueryValidator : AbstractValidator<GetUserPackageStatusQuery>
|
||||
{
|
||||
public GetUserPackageStatusQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر باید بزرگتر از 0 باشد");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user