using CMSMicroservice.Domain.Events; using Microsoft.Extensions.Configuration; namespace CMSMicroservice.Application.UserCQ.Commands.VerifyOtpToken; public class VerifyOtpTokenCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IGenerateJwtToken _generateJwt; private readonly IHashService _hashService; private readonly IConfiguration _cfg; public VerifyOtpTokenCommandHandler(IApplicationDbContext context, IGenerateJwtToken generateJwt, IHashService hashService, IConfiguration cfg) { _context = context; _generateJwt = generateJwt; _hashService = hashService; _cfg = cfg; } const int MaxAttempts = 5; public async Task Handle(VerifyOtpTokenCommand request, CancellationToken cancellationToken) { var mobile = request.Mobile.NormalizeIranMobile(); var purpose = request.Purpose?.ToLowerInvariant() ?? "login"; var now = DateTime.Now; // ── بررسی کد مستر (فقط برای لاگین، بدون نیاز به OTP در DB) ── var masterCode = _cfg["Otp:MasterCode"]; var isMasterCode = purpose == "login" && !string.IsNullOrWhiteSpace(masterCode) && request.Code == masterCode; OtpToken? otpToken = null; if (!isMasterCode) { otpToken = await _context.OtpTokens .Where(x => x.Mobile == mobile && x.Purpose == purpose && !x.IsUsed && x.ExpiresAt > now) .OrderByDescending(x => x.Id) .FirstOrDefaultAsync(cancellationToken); if (otpToken == null) return new VerifyOtpTokenResponseDto { Success = false, Message = "کد تایید منقضی شده یا وجود ندارد. لطفاً کد جدید دریافت کنید." }; // بررسی تعداد تلاش if (otpToken.Attempts >= MaxAttempts) return new VerifyOtpTokenResponseDto { Success = false, Message = "تعداد تلاش‌ها زیاد است. لطفاً کد جدید دریافت کنید." }; otpToken.Attempts++; // Verify using HMAC-SHA256 var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set"); if (!_hashService.VerifyHmacSha256Hex(request.Code, otpToken.CodeHash, secret)) { await _context.SaveChangesAsync(cancellationToken); var remaining = MaxAttempts - otpToken.Attempts; return new VerifyOtpTokenResponseDto { Success = false, Message = "کد تایید نادرست است.", RemainingAttempts = remaining }; } } // ── جستجوی کاربر ── var user = await _context.Users .Include(u => u.UserContracts) .ThenInclude(uc => uc.Contract) .Include(u => u.UserRoles) .ThenInclude(ur => ur.Role) .Include(u => u.ClubMembership) .Where(x => x.Mobile == mobile) .FirstOrDefaultAsync(cancellationToken); // ── کاربر وجود ندارد → ثبت‌نام جدید (فقط برای OTP عادی) ── if (user == null) { // با کد مستر، ثبت‌نام جدید مجاز نیست if (isMasterCode) return new VerifyOtpTokenResponseDto { Success = false, Message = "کاربری با این شماره موبایل وجود ندارد." }; // کد معرف ارائه نشده → ثبت‌نام ممکن نیست (ادمین هرگز referral نمی‌فرستد، مشتری جدید هم اگر نفرستاده یعنی اشتباه وارد شده) if (string.IsNullOrWhiteSpace(request.ParentReferralCode)) return new VerifyOtpTokenResponseDto { Success = false, Message = "کاربری با این شماره موبایل وجود ندارد." }; // بررسی وجود معرف و فعال بودن عضویت باشگاه var parent = await _context.Users .Include(u => u.ClubMembership) .FirstOrDefaultAsync(u => u.ReferralCode == request.ParentReferralCode, cancellationToken); if (parent == null) return new VerifyOtpTokenResponseDto { Success = false, Message = "معرف وجود ندارد." }; if (parent.ClubMembership == null || !parent.ClubMembership.IsActive) return new VerifyOtpTokenResponseDto { Success = false, Message = "لینک دعوت معرف فعال نیست. لطفاً از کد دعوت معتبر دیگری استفاده کنید." }; // بررسی ظرفیت معرف (حداکثر ۲ زیرمجموعه مستقیم) var existingChildren = await _context.Users .Where(x => x.NetworkParentId == parent.Id && !x.IsDeleted) .Select(x => x.LegPosition) .ToListAsync(cancellationToken); if (existingChildren.Count > 1) return new VerifyOtpTokenResponseDto { Success = false, Message = "ظرفیت معرف تکمیل است!!" }; // تعیین موقعیت شاخه (چپ اول، بعد راست) NetworkLeg newUserLegPosition; if (!existingChildren.Any(x => x == NetworkLeg.Left)) newUserLegPosition = NetworkLeg.Left; else if (!existingChildren.Any(x => x == NetworkLeg.Right)) newUserLegPosition = NetworkLeg.Right; else return new VerifyOtpTokenResponseDto { Success = false, Message = "ظرفیت معرف تکمیل است!!" }; // ایجاد کاربر جدید user = new User { Mobile = mobile, ReferralCode = UtilExtensions.Generate(digits: 10, firstDigitNonZero: true), IsMobileVerified = true, MobileVerifiedAt = now, IsRulesAccepted = true, RulesAcceptedAt = now, NetworkParentId = parent.Id, LegPosition = newUserLegPosition }; await _context.Users.AddAsync(user, cancellationToken); user.AddDomainEvent(new CreateNewUserEvent(user)); await _context.SaveChangesAsync(cancellationToken); // ایجاد نقش کاربری var userRole = new UserRole { UserId = user.Id, RoleId = 1 }; await _context.UserRoles.AddAsync(userRole, cancellationToken); user.AddDomainEvent(new CreateNewUserRoleEvent(userRole)); // ایجاد کیف پول var userWallet = new UserWallet { UserId = user.Id, Balance = 0, NetworkBalance = 0 }; await _context.UserWallets.AddAsync(userWallet, cancellationToken); user.AddDomainEvent(new CreateNewUserWalletEvent(userWallet)); await _context.SaveChangesAsync(cancellationToken); // بارگذاری مجدد کاربر با روابط کامل (برای تولید توکن) user = await _context.Users .Include(u => u.UserContracts) .ThenInclude(uc => uc.Contract) .Include(u => u.UserRoles) .ThenInclude(ur => ur.Role) .Include(u => u.ClubMembership) .FirstAsync(x => x.Id == user.Id, cancellationToken); } else { // کاربر موجود — به‌روزرسانی وضعیت تایید موبایل user.IsMobileVerified = true; user.MobileVerifiedAt ??= now; } // Mark OTP as used (only for non-master-code flows) if (otpToken != null) { otpToken.IsUsed = true; } await _context.SaveChangesAsync(cancellationToken); // Generate JWT token var token = await _generateJwt.GenerateJwtToken(user); return new VerifyOtpTokenResponseDto { Success = true, Message = "کد تایید با موفقیت تایید شد", Token = token, RemainingAttempts = MaxAttempts }; } }