using CMSMicroservice.Application.Common.Exceptions; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Common; using CMSMicroservice.Domain.Entities; using CMSMicroservice.Domain.Entities.Club; using CMSMicroservice.Domain.Entities.Commission; using CMSMicroservice.Domain.Entities.History; using CMSMicroservice.Domain.Enums; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract; /// /// Handler برای پذیرش قرارداد باشگاه مشتریان /// این handler: /// 1. کد OTP را تایید می‌کند /// 2. قرارداد را در جدول UserContract ثبت می‌کند /// 3. باشگاه مشتری را فعال می‌کند (IsActive = true) /// 4. مبلغ را به Pool هفته جاری اضافه می‌کند /// 5. ویژگی‌های باشگاه را به کاربر اعطا می‌کند /// public class AcceptClubMembershipContractCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IConfiguration _cfg; private readonly IHashService _hashService; private readonly ICurrentUserService _currentUser; private readonly IWeekDefinitionRepository _weekRepository; private readonly ILogger _logger; private const int MaxAttempts = 5; private const string OtpPurpose = "signClubContract"; public AcceptClubMembershipContractCommandHandler( IApplicationDbContext context, IConfiguration cfg, IHashService hashService, ICurrentUserService currentUser, IWeekDefinitionRepository weekRepository, ILogger logger) { _context = context; _cfg = cfg; _hashService = hashService; _currentUser = currentUser; _weekRepository = weekRepository; _logger = logger; } public async Task Handle( AcceptClubMembershipContractCommand request, CancellationToken cancellationToken) { // خواندن UserId از JWT (امن‌تر از دریافت از کلاینت) if (!long.TryParse(_currentUser.UserId, out var userId)) return new AcceptClubMembershipContractResponseDto { Success = false, Message = "کاربر احراز هویت نشده است" }; _logger.LogInformation( "Processing club membership contract for UserId: {UserId}", userId ); // 1. دریافت کاربر var user = await _context.Users .Include(u => u.ClubMembership) .FirstOrDefaultAsync(u => u.Id == userId, cancellationToken); if (user == null) { _logger.LogWarning("User not found: {UserId}", userId); throw new NotFoundException(nameof(User), userId); } // 2. بررسی خرید پکیج if (user.PackagePurchaseMethod == PackagePurchaseMethod.None) { return new AcceptClubMembershipContractResponseDto { Success = false, Message = "برای امضای قرارداد باشگاه مشتریان ابتدا باید پکیج پایه را خریداری کنید" }; } // 3. بررسی عدم فعال بودن قبلی باشگاه if (user.ClubMembership?.IsActive == true) { return new AcceptClubMembershipContractResponseDto { Success = false, Message = "شما قبلاً عضو باشگاه مشتریان شده‌اید" }; } // 4. تایید OTP var otpResult = await VerifyOtpAsync(user.Mobile, request.OtpCode, cancellationToken); if (!otpResult.Success) { return new AcceptClubMembershipContractResponseDto { Success = false, Message = otpResult.Message }; } // 5. ثبت قرارداد در جدول UserContract var contract = await _context.Contracts .FirstOrDefaultAsync(c => c.Type == ContractType.ClubMembership, cancellationToken); if (contract == null) { // اگر قرارداد وجود ندارد، یک قرارداد پیش‌فرض ایجاد کنید contract = new Contract { Title = "قرارداد باشگاه مشتریان", Description = "قوانین و مقررات باشگاه مشتریان کارابازار", HtmlContent = request.ContractHtml, Type = ContractType.ClubMembership }; await _context.Contracts.AddAsync(contract, cancellationToken); await _context.SaveChangesAsync(cancellationToken); } var userContract = new UserContract { UserId = user.Id, ContractId = contract.Id, SignGuid = request.SignGuid, SignedPdfFile = request.ContractHtml }; await _context.UserContracts.AddAsync(userContract, cancellationToken); // 6. دریافت مقادیر از SystemConstants (استاتیک) long giftValue = SystemConstants.ClubMembershipGiftValue; long activationFeeValue = SystemConstants.ClubActivationFee; _logger.LogInformation( "Using Club.MembershipGiftValue: {GiftValue}, Club.ActivationFee: {ActivationFee}", giftValue, activationFeeValue ); // 7. فعال‌سازی باشگاه مشتریان ClubMembership clubMembership; bool isNewMembership = user.ClubMembership == null; var activationDate = DateTime.Now; if (isNewMembership) { clubMembership = new ClubMembership { UserId = user.Id, IsActive = true, ActivatedAt = activationDate, InitialContribution = activationFeeValue, GiftValue = giftValue, TotalEarned = 0, PurchaseMethod = user.PackagePurchaseMethod }; await _context.ClubMemberships.AddAsync(clubMembership, cancellationToken); _logger.LogInformation( "Created new club membership for UserId {UserId} via {Method}, GiftValue: {GiftValue}", user.Id, user.PackagePurchaseMethod, giftValue ); } else { clubMembership = user.ClubMembership!; clubMembership.IsActive = true; clubMembership.ActivatedAt = activationDate; clubMembership.PurchaseMethod = user.PackagePurchaseMethod; _context.ClubMemberships.Update(clubMembership); _logger.LogInformation( "Reactivated club membership for UserId {UserId}", user.Id ); } await _context.SaveChangesAsync(cancellationToken); // 8. ثبت تاریخچه var history = new ClubMembershipHistory { ClubMembershipId = clubMembership.Id, UserId = clubMembership.UserId, OldIsActive = !isNewMembership && !user.ClubMembership!.IsActive, NewIsActive = true, Action = ClubMembershipAction.Activated, Reason = isNewMembership ? $"Initial activation via contract signing - {user.PackagePurchaseMethod}" : $"Reactivated via contract signing - {user.PackagePurchaseMethod}", PerformedBy = _currentUser.GetPerformedBy() }; _context.ClubMembershipHistories.Add(history); await _context.SaveChangesAsync(cancellationToken); // 9. اضافه کردن مبلغ به Pool هفته جاری var currentWeekDefinitionId = GetCurrentWeekDefinitionId(); var weeklyPool = await _context.WeeklyCommissionPools .FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId, cancellationToken); if (weeklyPool == null) { weeklyPool = new WeeklyCommissionPool { WeekDefinitionId = currentWeekDefinitionId, TotalPoolAmount = activationFeeValue, TotalBalances = 0, ValuePerBalance = 0, IsCalculated = false, CalculatedAt = null }; await _context.WeeklyCommissionPools.AddAsync(weeklyPool, cancellationToken); _logger.LogInformation( "Created new WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId} with initial amount: {Amount}", currentWeekDefinitionId, activationFeeValue ); } else { weeklyPool.TotalPoolAmount += activationFeeValue; _context.WeeklyCommissionPools.Update(weeklyPool); _logger.LogInformation( "Added {Amount} to existing WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId}. New total: {NewTotal}", activationFeeValue, currentWeekDefinitionId, weeklyPool.TotalPoolAmount ); } await _context.SaveChangesAsync(cancellationToken); // 10. اعطای ویژگی‌های باشگاه برای کاربر (فقط برای عضویت جدید) if (isNewMembership) { var featureIds = ClubFeatureTypeExtensions.GetAllFeatureIds(); var clubFeatures = await _context.ClubFeatures .Where(f => !f.IsDeleted && featureIds.Contains(f.Id)) .ToListAsync(cancellationToken); if (clubFeatures.Any()) { var userClubFeatures = clubFeatures.Select(feature => new UserClubFeature { UserId = user.Id, ClubMembershipId = clubMembership.Id, ClubFeatureId = feature.Id, GrantedAt = activationDate, IsActive = true, Notes = "اعطا شده هنگام امضای قرارداد" }).ToList(); _context.UserClubFeatures.AddRange(userClubFeatures); await _context.SaveChangesAsync(cancellationToken); _logger.LogInformation( "Granted {Count} club features to UserId {UserId}", clubFeatures.Count, user.Id ); } } _logger.LogInformation( "Club membership contract accepted and activated for UserId: {UserId}, ContractId: {ContractId}, MembershipId: {MembershipId}", request.UserId, userContract.Id, clubMembership.Id ); return new AcceptClubMembershipContractResponseDto { Success = true, Message = "قرارداد باشگاه مشتریان با موفقیت ثبت شد و عضویت شما فعال گردید", ContractId = userContract.Id }; } /// /// دریافت شناسه تعریف هفته جاری /// private long GetCurrentWeekDefinitionId() { var week = _weekRepository.GetCurrentWeek(); if (week == null) { throw new InvalidOperationException("هفته جاری در سیستم تعریف نشده است"); } return week.Id; } private async Task<(bool Success, string Message)> VerifyOtpAsync( string mobile, string code, CancellationToken cancellationToken) { var normalizedMobile = mobile.NormalizeIranMobile(); var now = DateTime.Now; var otp = await _context.OtpTokens .Where(o => o.Mobile == normalizedMobile && o.Purpose == OtpPurpose && !o.IsUsed && o.ExpiresAt > now) .OrderByDescending(o => o.Created) .FirstOrDefaultAsync(cancellationToken); if (otp == null) { return (false, "کد تایید پیدا نشد یا منقضی شده است"); } if (otp.Attempts >= MaxAttempts) { return (false, "تعداد تلاش‌ها بیش از حد مجاز است. لطفاً کد جدید دریافت کنید"); } otp.Attempts++; var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not configured"); if (!_hashService.VerifyHmacSha256Hex(code, otp.CodeHash, secret)) { await _context.SaveChangesAsync(cancellationToken); return (false, "کد تایید نادرست است"); } // کد صحیح است - علامت‌گذاری به عنوان استفاده شده otp.IsUsed = true; await _context.SaveChangesAsync(cancellationToken); return (true, "کد تایید صحیح است"); } }