183 lines
8.4 KiB
C#
183 lines
8.4 KiB
C#
using CMSMicroservice.Domain.Events;
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
namespace CMSMicroservice.Application.UserCQ.Commands.VerifyOtpToken;
|
|
public class VerifyOtpTokenCommandHandler : IRequestHandler<VerifyOtpTokenCommand, VerifyOtpTokenResponseDto>
|
|
{
|
|
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<VerifyOtpTokenResponseDto> 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
|
|
};
|
|
}
|
|
}
|