Files
CMS/src/CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs
T
masoodafar-web 61b7e4f8f2
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m29s
F2-F7: نوتیفیکیشن‌ها+نام‌پکیج، ستون پکیج در CSV، مقادیر داینامیک کیف‌پول جادویی، ولیدیتور SystemConstants
F2: اضافه شدن packageName به SmsTemplates، IUserNotificationService، UserNotificationService
    - DayaLoan، ClubActivated، CommissionDeposited حالا نام پکیج را نشان می‌دهند
F4: اضافه شدن ستون پکیج به CSV خروجی‌ها
    - commission.proto: package_name در WithdrawalRequestModel
    - manualpayment.proto: package_name در ManualPaymentModel
    - کوئری‌هندلرها UserPackagePurchases لوکاپ اضافه شد
F6: مقادیر داینامیک کیف‌پول جادویی از پکیج
    - userwallet.proto: magic_multiplier + magic_max_credit
    - UserWalletService: پاپیولیت فیلدهای جدید + فالبک به SystemConstants
F7: ولیدیتورها از SystemConstants استفاده می‌کنند
    - WalletMaxSafeAmount (10B ریال) به عنوان حصار ایمنی
    - MagicWalletMinCharge، DiscountWalletMinCharge ثابت‌های مرکزی
    - سقف واقعی per-package در هندلرها اعمال می‌شود
Proto: v0.0.189
2026-02-27 08:47:46 +03:30

312 lines
13 KiB
C#

using CMSMicroservice.Domain.Events;
using CMSMicroservice.Domain.Enums;
using CMSMicroservice.Domain.Common;
using CMSMicroservice.Application.DayaLoanCQ.Services;
using CMSMicroservice.Application.Common.Interfaces;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.DayaLoanCQ.Commands.CheckAndProcessDayaLoans;
/// <summary>
/// Handler یکپارچه برای استعلام و پردازش وام دایا
/// 1. استعلام از API دایا
/// 2. ذخیره/به‌روزرسانی DayaLoanContract
/// 3. شارژ کیف پول برای وام‌های تأیید شده
/// 4. ثبت Order پکیج
/// 5. ارسال SMS
/// </summary>
public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndProcessDayaLoansCommand, CheckAndProcessDayaLoansResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly IDayaLoanApiService _dayaApiService;
private readonly IKavenegarService _smsService;
private readonly ILogger<CheckAndProcessDayaLoansCommandHandler> _logger;
public CheckAndProcessDayaLoansCommandHandler(
IApplicationDbContext context,
IDayaLoanApiService dayaApiService,
IKavenegarService smsService,
ILogger<CheckAndProcessDayaLoansCommandHandler> logger)
{
_context = context;
_dayaApiService = dayaApiService;
_smsService = smsService;
_logger = logger;
}
public async Task<CheckAndProcessDayaLoansResponseDto> Handle(CheckAndProcessDayaLoansCommand request, CancellationToken cancellationToken)
{
var results = new List<DayaLoanProcessResult>();
var processedCount = 0;
try
{
// 1. استعلام از سرویس دایا
_logger.LogInformation("Checking Daya loan status for {Count} national codes", request.NationalCodes.Count);
var dayaResults = await _dayaApiService.CheckLoanStatusAsync(request.NationalCodes, cancellationToken);
foreach (var dayaResult in dayaResults)
{
var result = new DayaLoanProcessResult
{
NationalCode = dayaResult.NationalCode,
Status = dayaResult.Status,
ContractNumber = dayaResult.ContractNumber,
WasProcessed = false,
Message = "استعلام موفق"
};
try
{
// 2. پیدا کردن کاربر
var user = await _context.Users
.Include(u => u.UserWallets)
.FirstOrDefaultAsync(u => u.NationalCode == dayaResult.NationalCode, cancellationToken);
if (user == null)
{
result.Message = "کاربر یافت نشد";
results.Add(result);
continue;
}
result.UserId = user.Id;
// 2.5. بررسی محدودیت دایا — پس از اولین دور، فقط IPG مجاز است
var hasPreviousCycle = await _context.ClubMembershipCycles
.AnyAsync(c => c.UserId == user.Id, cancellationToken);
if (hasPreviousCycle)
{
result.Message = "کاربرانی که دور اول را تکمیل کرده‌اند فقط از طریق درگاه بانکی می‌توانند پکیج خریداری کنند";
results.Add(result);
continue;
}
// 3. ذخیره/به‌روزرسانی DayaLoanContract
var contract = await _context.DayaLoanContracts
.FirstOrDefaultAsync(d => d.NationalCode == dayaResult.NationalCode, cancellationToken);
if (contract == null)
{
contract = new DayaLoanContract
{
UserId = user.Id,
NationalCode = dayaResult.NationalCode,
Status = dayaResult.Status,
ContractNumber = dayaResult.ContractNumber,
LastCheckDate = DateTime.Now,
IsProcessed = false
};
await _context.DayaLoanContracts.AddAsync(contract, cancellationToken);
}
else
{
contract.Status = dayaResult.Status;
contract.ContractNumber = dayaResult.ContractNumber;
contract.LastCheckDate = DateTime.Now;
}
// 4. آیا باید پردازش مالی انجام شود؟
var shouldProcess = !contract.IsProcessed &&
!user.HasReceivedDayaCredit &&
!string.IsNullOrEmpty(dayaResult.ContractNumber) &&
(dayaResult.Status == DayaLoanStatus.PendingReceive ||
dayaResult.Status == DayaLoanStatus.Received);
if (shouldProcess)
{
// 5. پردازش مالی
var processResult = await ProcessDayaLoanAsync(user, dayaResult.ContractNumber!, cancellationToken);
contract.IsProcessed = true;
result.WasProcessed = true;
result.NewWalletBalance = processResult.NewBalance;
result.PackageName = processResult.PackageName;
result.Message = "وام پردازش و کیف پول شارژ شد";
processedCount++;
_logger.LogInformation(
"Daya loan processed for User {UserId}, Contract: {ContractNumber}, NewBalance: {Balance}",
user.Id, dayaResult.ContractNumber, processResult.NewBalance);
}
else if (contract.IsProcessed || user.HasReceivedDayaCredit)
{
result.Message = "قبلاً پردازش شده";
}
else if (string.IsNullOrEmpty(dayaResult.ContractNumber))
{
result.Message = "هنوز قرارداد ندارد";
}
await _context.SaveChangesAsync(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing Daya loan for NationalCode {NationalCode}", dayaResult.NationalCode);
result.Message = $"خطا: {ex.Message}";
}
results.Add(result);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error calling Daya API service");
// در صورت خطا در API
foreach (var nationalCode in request.NationalCodes)
{
if (!results.Any(r => r.NationalCode == nationalCode))
{
results.Add(new DayaLoanProcessResult
{
NationalCode = nationalCode,
Status = DayaLoanStatus.PendingReceive,
WasProcessed = false,
Message = $"خطا در استعلام: {ex.Message}"
});
}
}
}
return new CheckAndProcessDayaLoansResponseDto
{
Results = results,
TotalChecked = request.NationalCodes.Count,
WithContractCount = results.Count(r => !string.IsNullOrEmpty(r.ContractNumber)),
ProcessedCount = processedCount
};
}
/// <summary>
/// پردازش مالی وام دایا
/// </summary>
private async Task<(long NewBalance, long TransactionId, string PackageName)> ProcessDayaLoanAsync(
User user,
string contractNumber,
CancellationToken cancellationToken)
{
// بارگذاری پکیج پایه
var package = await _context.Packages
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
?? throw new InvalidOperationException("پکیج پایه یافت نشد");
// 1. ایجاد تراکنش
var transaction = new Transaction
{
Amount = package.Price,
Description = $"دریافت اعتبار دایا - قرارداد {contractNumber}",
PaymentStatus = PaymentStatus.Success,
PaymentDate = DateTime.Now,
RefId = contractNumber,
Type = TransactionType.DepositExternal1
};
await _context.Transactions.AddAsync(transaction, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
// 2. یافتن یا ایجاد کیف پول
var wallet = user.UserWallets.FirstOrDefault();
if (wallet == null)
{
wallet = new UserWallet
{
UserId = user.Id,
Balance = 0,
NetworkBalance = 0,
DiscountBalance = 0
};
await _context.UserWallets.AddAsync(wallet, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
}
// 3. شارژ کیف پول عادی
wallet.Balance += package.Price;
var mainLog = new UserWalletHistory
{
WalletId = wallet.Id,
CurrentBalance = 0,
ChangeValue = package.Price,
CurrentNetworkBalance = wallet.NetworkBalance,
ChangeNerworkValue = 0,
IsIncrease = true,
RefrenceId = transaction.Id,
PackageId = package.Id
};
await _context.UserWalletHistories.AddAsync(mainLog, cancellationToken);
// 4. شارژ کیف پول تخفیف
var discountAmount = (long)(package.Price * package.DiscountMultiplier);
wallet.DiscountBalance += discountAmount;
var discountLog = new UserWalletHistory
{
WalletId = wallet.Id,
CurrentBalance = wallet.Balance,
ChangeValue = 0,
CurrentNetworkBalance = wallet.NetworkBalance,
ChangeNerworkValue = 0,
CurrentDiscountBalance = 0,
ChangeDiscountValue = discountAmount,
IsIncrease = true,
RefrenceId = transaction.Id,
PackageId = package.Id
};
await _context.UserWalletHistories.AddAsync(discountLog, cancellationToken);
// 5. به‌روزرسانی کاربر
user.HasReceivedDayaCredit = true;
user.DayaCreditReceivedAt = DateTime.Now;
user.PackagePurchaseMethod = PackagePurchaseMethod.DayaLoan;
// 6. ثبت UserPackagePurchase برای پکیج پایه
var packagePurchase = new UserPackagePurchase
{
UserId = user.Id,
PackageId = package.Id,
PurchaseMethod = PackagePurchaseMethod.DayaLoan,
PurchasedAt = DateTime.Now,
Amount = package.Price,
TransactionId = transaction.Id
};
await _context.UserPackagePurchases.AddAsync(packagePurchase, cancellationToken);
// 7. Domain Event
user.AddDomainEvent(new DayaLoanApprovedEvent(user, transaction, contractNumber));
await _context.SaveChangesAsync(cancellationToken);
// 8. ارسال SMS
await SendDayaLoanSmsAsync(user, package.Title);
return (wallet.Balance, transaction.Id, package.Title);
}
/// <summary>
/// ارسال SMS اطلاع‌رسانی
/// </summary>
private async Task SendDayaLoanSmsAsync(User user, string packageName)
{
try
{
var userName = SmsTemplates.GetUserName(user.FirstName+" "+user.LastName);
var message = SmsTemplates.DayaLoanReceived(userName, packageName);
await _smsService.SendAsync(user.Mobile, message);
// ارسال SMS به ادمین برای اطلاع
var adminMessage = $"وام دایا دریافت شد\nکاربر: {user.FirstName} {user.LastName}\nموبایل: {user.Mobile}\nکدملی: {user.NationalCode}\nپکیج: {packageName}";
await _smsService.SendAsync("09199877503", adminMessage);
_logger.LogInformation("Daya loan SMS sent to User {UserId}", user.Id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to send Daya loan SMS to User {UserId}", user.Id);
}
}
}