feat: Add ClearCart command and response, implement CancelOrder command with validation, and enhance DeliveryStatus and User models

This commit is contained in:
masoodafar-web
2025-12-02 03:30:36 +03:30
parent 25fc73ae28
commit 78606cc5cc
100 changed files with 12925 additions and 8137 deletions

View File

@@ -0,0 +1,18 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Enums;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
/// <summary>
/// دستور خرید پکیج طلایی از طریق درگاه بانکی
/// </summary>
public class PurchaseGoldenPackageCommand : IRequest<PaymentInitiateResult>
{
/// <summary>
/// شناسه کاربر
/// </summary>
public long UserId { get; set; }
}

View File

@@ -0,0 +1,160 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Enums;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ValidationException = FluentValidation.ValidationException;
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
public class PurchaseGoldenPackageCommandHandler
: IRequestHandler<PurchaseGoldenPackageCommand, PaymentInitiateResult>
{
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<PaymentInitiateResult> Handle(
PurchaseGoldenPackageCommand request,
CancellationToken cancellationToken)
{
try
{
_logger.LogInformation(
"Starting golden package purchase for UserId: {UserId}",
request.UserId
);
// 1. بررسی وجود کاربر
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
if (user == null)
{
_logger.LogWarning("User not found: {UserId}", request.UserId);
throw new NotFoundException(nameof(User), request.UserId);
}
// 2. بررسی اینکه قبلاً پکیج نخریده باشد
if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
{
_logger.LogWarning(
"User {UserId} has already purchased package via {Method}",
request.UserId,
user.PackagePurchaseMethod
);
throw new ValidationException(
"شما قبلاً پکیج طلایی را خریداری کرده‌اید"
);
}
// 3. پیدا کردن پکیج طلایی
var goldenPackage = await _context.Packages
.FirstOrDefaultAsync(
p => p.Title.Contains("طلایی") || p.Title.Contains("Golden"),
cancellationToken
);
if (goldenPackage == null)
{
_logger.LogError("Golden package not found in database");
throw new NotFoundException("پکیج طلایی یافت نشد");
}
// 4. پیدا کردن آدرس پیش‌فرض کاربر (برای فیلد اجباری)
var defaultAddress = await _context.UserAddresss
.Where(a => a.UserId == request.UserId)
.OrderByDescending(a => a.Created)
.FirstOrDefaultAsync(cancellationToken);
if (defaultAddress == null)
{
_logger.LogWarning("No address found for user {UserId}", request.UserId);
throw new ValidationException(
"لطفاً ابتدا یک آدرس برای خود ثبت کنید"
);
}
// 5. ایجاد سفارش
var order = new UserOrder
{
UserId = user.Id,
PackageId = goldenPackage.Id,
Amount = goldenPackage.Price, // 56,000,000 تومان
PaymentStatus = PaymentStatus.Pending,
DeliveryStatus = DeliveryStatus.None,
UserAddressId = defaultAddress.Id,
PaymentMethod = PaymentMethod.IPG
};
_context.UserOrders.Add(order);
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"Created UserOrder {OrderId} for UserId {UserId}, Amount: {Amount}",
order.Id,
request.UserId,
order.Amount
);
// 6. ایجاد درخواست پرداخت از درگاه
var paymentRequest = new PaymentRequest
{
Amount = order.Amount,
UserId = user.Id,
Mobile = user.Mobile ?? "",
CallbackUrl = $"https://yourdomain.com/api/package/verify-golden-package",
Description = $"خرید پکیج طلایی - سفارش #{order.Id}"
};
var paymentResult = await _paymentGateway.InitiatePaymentAsync(paymentRequest);
if (!paymentResult.IsSuccess)
{
_logger.LogError(
"Payment gateway failed for OrderId {OrderId}: {ErrorMessage}",
order.Id,
paymentResult.ErrorMessage
);
// به‌روزرسانی وضعیت سفارش
order.PaymentStatus = PaymentStatus.Reject;
await _context.SaveChangesAsync(cancellationToken);
throw new Exception(
$"خطا در ارتباط با درگاه پرداخت: {paymentResult.ErrorMessage}"
);
}
_logger.LogInformation(
"Payment initiated successfully. OrderId: {OrderId}, RefId: {RefId}",
order.Id,
paymentResult.RefId
);
return paymentResult;
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Error in PurchaseGoldenPackageCommand for UserId: {UserId}",
request.UserId
);
throw;
}
}
}

View File

@@ -0,0 +1,13 @@
using FluentValidation;
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
public class PurchaseGoldenPackageCommandValidator : AbstractValidator<PurchaseGoldenPackageCommand>
{
public PurchaseGoldenPackageCommandValidator()
{
RuleFor(x => x.UserId)
.GreaterThan(0)
.WithMessage("شناسه کاربر باید بزرگتر از صفر باشد");
}
}

View File

@@ -0,0 +1,21 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using MediatR;
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
/// <summary>
/// دستور تأیید پرداخت پکیج طلایی و شارژ کیف پول
/// </summary>
public class VerifyGoldenPackagePurchaseCommand : IRequest<bool>
{
/// <summary>
/// شناسه سفارش
/// </summary>
public long OrderId { get; set; }
/// <summary>
/// کد Authority از درگاه
/// </summary>
public string Authority { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,189 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Enums;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ValidationException = FluentValidation.ValidationException;
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
public class VerifyGoldenPackagePurchaseCommandHandler
: IRequestHandler<VerifyGoldenPackagePurchaseCommand, bool>
{
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<bool> Handle(
VerifyGoldenPackagePurchaseCommand request,
CancellationToken cancellationToken)
{
try
{
_logger.LogInformation(
"Verifying golden package purchase. OrderId: {OrderId}, Authority: {Authority}",
request.OrderId,
request.Authority
);
// 1. پیدا کردن سفارش
var order = await _context.UserOrders
.Include(o => o.Package)
.Include(o => o.User)
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
if (order == null)
{
_logger.LogWarning("Order not found: {OrderId}", request.OrderId);
throw new NotFoundException(nameof(UserOrder), request.OrderId);
}
// 2. بررسی اینکه سفارش قبلاً پرداخت نشده باشد
if (order.PaymentStatus == PaymentStatus.Success)
{
_logger.LogWarning("Order {OrderId} is already paid", request.OrderId);
return true;
}
// 3. Verify با درگاه بانکی
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
request.Authority,
request.Authority // verificationToken - در بعضی درگاه‌ها همان Authority است
);
if (!verifyResult.IsSuccess)
{
_logger.LogWarning(
"Payment verification failed for OrderId {OrderId}: {Message}",
request.OrderId,
verifyResult.Message
);
order.PaymentStatus = PaymentStatus.Reject;
await _context.SaveChangesAsync(cancellationToken);
throw new ValidationException($"تراکنش ناموفق: {verifyResult.Message}");
}
// 4. شارژ کیف پول کاربر
var wallet = await _context.UserWallets
.FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken);
if (wallet == null)
{
_logger.LogError("Wallet not found for UserId: {UserId}", order.UserId);
throw new NotFoundException($"کیف پول کاربر با شناسه {order.UserId} یافت نشد");
}
// شارژ Balance (موجودی عادی)
var oldBalance = wallet.Balance;
wallet.Balance += order.Amount;
_logger.LogInformation(
"Charging Balance for UserId {UserId}: {OldBalance} -> {NewBalance}",
order.UserId,
oldBalance,
wallet.Balance
);
// شارژ DiscountBalance (موجودی تخفیف)
var oldDiscountBalance = wallet.DiscountBalance;
wallet.DiscountBalance += order.Amount;
_logger.LogInformation(
"Charging DiscountBalance for UserId {UserId}: {OldBalance} -> {NewBalance}",
order.UserId,
oldDiscountBalance,
wallet.DiscountBalance
);
// 5. ثبت Transaction
var transaction = new Transactions
{
Amount = order.Amount,
Description = $"خرید پکیج طلایی از درگاه - سفارش #{order.Id}",
PaymentStatus = PaymentStatus.Success,
PaymentDate = DateTime.UtcNow,
RefId = verifyResult.RefId,
Type = TransactionType.DepositIpg
};
_context.Transactionss.Add(transaction);
await _context.SaveChangesAsync(cancellationToken);
// 6. ثبت لاگ تغییر Balance
var balanceLog = new UserWalletChangeLog
{
WalletId = wallet.Id,
CurrentBalance = wallet.Balance,
ChangeValue = order.Amount,
CurrentNetworkBalance = wallet.NetworkBalance,
ChangeNerworkValue = 0,
CurrentDiscountBalance = wallet.DiscountBalance - order.Amount, // قبل از شارژ DiscountBalance
ChangeDiscountValue = 0,
IsIncrease = true,
RefrenceId = transaction.Id
};
await _context.UserWalletChangeLogs.AddAsync(balanceLog, cancellationToken);
// 7. ثبت لاگ تغییر DiscountBalance
var discountLog = new UserWalletChangeLog
{
WalletId = wallet.Id,
CurrentBalance = wallet.Balance,
ChangeValue = 0,
CurrentNetworkBalance = wallet.NetworkBalance,
ChangeNerworkValue = 0,
CurrentDiscountBalance = wallet.DiscountBalance,
ChangeDiscountValue = order.Amount,
IsIncrease = true,
RefrenceId = transaction.Id
};
await _context.UserWalletChangeLogs.AddAsync(discountLog, cancellationToken);
// 8. به‌روزرسانی Order
order.TransactionId = transaction.Id;
order.PaymentStatus = PaymentStatus.Success;
order.PaymentDate = DateTime.UtcNow;
order.PaymentMethod = PaymentMethod.IPG;
// 9. تغییر User.PackagePurchaseMethod
order.User.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase;
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"Golden package purchase verified successfully. " +
"OrderId: {OrderId}, UserId: {UserId}, TransactionId: {TransactionId}, RefId: {RefId}",
order.Id,
order.UserId,
transaction.Id,
verifyResult.RefId
);
return true;
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Error in VerifyGoldenPackagePurchaseCommand. OrderId: {OrderId}",
request.OrderId
);
throw;
}
}
}