Files
CMS/src/CMSMicroservice.WebApi/Services/UserService.cs
T
masoodafar-web 1e7c17f090
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m22s
feat: Update payment processing and callback mechanisms
- Refactor ActivateClubMembershipCommandHandler to use UserPackagePurchases instead of UserOrders for package activation.
- Modify PlaceOrderCommandHandler to redirect payment callbacks to the front office.
- Update ChargeDiscountWalletCommandHandler and ChargeMagicWalletCommandHandler to direct payment callbacks to the front office.
- Remove PaymentCallbackController and integrate payment verification directly into DiscountOrderService and UserWalletService.
- Add CustomerVerifyDiscountOrderPayment RPC to DiscountOrderService for verifying discount order payments.
- Implement VerifyMagicCharge and VerifyDiscountCharge methods in UserWalletService for wallet charge verifications.
- Update appsettings.json to use local URLs for development.
- Remove appsettings.Development.json as it is no longer needed.
- Comment out history tracking methods in ClubMembershipCycle and Package classes.
- Update PackageService to automatically activate club membership after successful payment verification.
- Adjust UserService to generate JWT tokens with user details.
2026-02-28 03:27:46 +03:30

433 lines
19 KiB
C#

using CMSMicroservice.Protobuf.Protos.User;
using CMSMicroservice.Protobuf.Protos.City;
using CMSMicroservice.WebApi.Common.Services;
using CMSMicroservice.Application.UserCQ.Commands.CreateNewUser;
using CMSMicroservice.Application.UserCQ.Commands.UpdateUser;
using CMSMicroservice.Application.UserCQ.Commands.DeleteUser;
using CMSMicroservice.Application.UserCQ.Queries.GetUser;
using CMSMicroservice.Application.UserCQ.Queries.GetAllUserByFilter;
using CMSMicroservice.Application.UserCQ.Queries.GetJwtToken;
using CMSMicroservice.Application.UserCQ.Queries.AdminGetJwtToken;
using CMSMicroservice.Application.UserCQ.Commands.SetPasswordForUser;
using CMSMicroservice.Application.UserCQ.Commands.RefreshToken;
using CMSMicroservice.Application.UserCQ.Commands.CreateNewOtpToken;
using CMSMicroservice.Application.UserCQ.Commands.VerifyOtpToken;
using CMSMicroservice.Application.UserCQ.Commands.AcceptContract;
using CMSMicroservice.Application.UserCQ.Queries.GetCustomerProfile;
using CMSMicroservice.Application.UserCQ.Queries.GetCustomerReferrals;
using CMSMicroservice.Application.UserCQ.Queries.GetCustomerSettings;
using CMSMicroservice.Application.Common.Interfaces;
using Google.Protobuf.WellKnownTypes;
using Microsoft.EntityFrameworkCore;
using Grpc.Core;
using System.Collections.Generic;
using System.Linq;
using MediatR;
using Mapster;
using AppModels = CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.WebApi.Services;
public class UserService : UserContract.UserContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
private readonly ISender _sender;
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUserService;
private readonly IHashService _hashService;
private readonly CMSMicroservice.Application.Common.FileManager.IFileManager _fileManager;
private readonly IGenerateJwtToken _generateJwt;
public UserService(
IDispatchRequestToCQRS dispatchRequestToCQRS,
ISender sender,
IApplicationDbContext context,
ICurrentUserService currentUserService,
IHashService hashService,
CMSMicroservice.Application.Common.FileManager.IFileManager fileManager,
IGenerateJwtToken generateJwt)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
_sender = sender;
_context = context;
_currentUserService = currentUserService;
_hashService = hashService;
_fileManager = fileManager;
_generateJwt = generateJwt;
}
public override async Task<CreateNewUserResponse> CreateNewUser(CreateNewUserRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<CreateNewUserRequest, CreateNewUserCommand, CreateNewUserResponse>(request, context);
}
public override async Task<Empty> UpdateUser(UpdateUserRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<UpdateUserRequest, UpdateUserCommand>(request, context);
}
public override async Task<Empty> DeleteUser(DeleteUserRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<DeleteUserRequest, DeleteUserCommand>(request, context);
}
public override async Task<GetUserResponse> GetUser(GetUserRequest request, ServerCallContext context)
{
// اگر Id ارسال نشده، از JWT بخون (برای کلاینت مشتری)
if (request.Id == 0)
request.Id = GetCurrentUserId();
return await _dispatchRequestToCQRS.Handle<GetUserRequest, GetUserQuery, GetUserResponse>(request, context);
}
public override async Task<GetAllUserByFilterResponse> GetAllUserByFilter(GetAllUserByFilterRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetAllUserByFilterRequest, GetAllUserByFilterQuery, GetAllUserByFilterResponse>(request, context);
}
public override async Task<GetJwtTokenResponse> GetJwtToken(GetJwtTokenRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetJwtTokenRequest, GetJwtTokenQuery, GetJwtTokenResponse>(request, context);
}
public override async Task<AdminGetJwtTokenResponse> AdminGetJwtToken(AdminGetJwtTokenRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<AdminGetJwtTokenRequest, AdminGetJwtTokenQuery, AdminGetJwtTokenResponse>(request, context);
}
public override async Task<Empty> SetPasswordForUser(SetPasswordForUserRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<SetPasswordForUserRequest, SetPasswordForUserCommand>(request, context);
}
public override async Task<RefreshTokenResponse> RefreshToken(RefreshTokenRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<RefreshTokenRequest, RefreshTokenCommand, RefreshTokenResponse>(request, context);
}
// ============= Customer-specific Methods =============
public override async Task<CreateNewOtpTokenResponse> CreateNewOtpToken(CreateNewOtpTokenRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<CreateNewOtpTokenRequest, CreateNewOtpTokenCommand, CreateNewOtpTokenResponse>(request, context);
}
public override async Task<VerifyOtpTokenResponse> VerifyOtpToken(VerifyOtpTokenRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<VerifyOtpTokenRequest, VerifyOtpTokenCommand, VerifyOtpTokenResponse>(request, context);
}
public override async Task<AcceptContractResponse> AcceptContract(AcceptContractRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<AcceptContractRequest, AcceptContractCommand, AcceptContractResponse>(request, context);
}
public override async Task<GetUserForCustomerResponse> GetUserForCustomer(GetUserForCustomerRequest request, ServerCallContext context)
{
var userId = GetCurrentUserId();
var user = await _context.Users
.AsNoTracking()
.Include(u => u.UserContracts)
.ThenInclude(uc => uc.Contract)
.Include(u => u.UserRoles)
.ThenInclude(ur => ur.Role)
.Include(u => u.ClubMembership)
.Where(u => u.Id == userId && !u.IsDeleted)
.FirstOrDefaultAsync(context.CancellationToken);
if (user == null)
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
// تولید توکن JWT با آخرین اطلاعات کاربر
var token = await _generateJwt.GenerateJwtToken(user);
return new GetUserForCustomerResponse
{
Id = user.Id,
FirstName = user.FirstName ?? string.Empty,
LastName = user.LastName ?? string.Empty,
Mobile = user.Mobile,
Email = user.Email ?? string.Empty,
NationalCode = user.NationalCode ?? string.Empty,
AvatarPath = user.AvatarPath ?? string.Empty,
ParentId = user.NetworkParentId,
ReferralCode = user.ReferralCode ?? string.Empty,
IsMobileVerified = user.IsMobileVerified,
MobileVerifiedAt = user.MobileVerifiedAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(user.MobileVerifiedAt.Value, DateTimeKind.Utc))
: null,
EmailNotifications = user.EmailNotifications,
SmsNotifications = user.SmsNotifications,
PushNotifications = user.PushNotifications,
BirthDate = user.BirthDate.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(user.BirthDate.Value, DateTimeKind.Utc))
: null,
Token = token
};
}
public override async Task<Empty> UpdateCustomerProfile(UpdateCustomerProfileRequest request, ServerCallContext context)
{
var userId = GetCurrentUserId();
var user = await _context.Users
.Where(u => u.Id == userId && !u.IsDeleted)
.FirstOrDefaultAsync(context.CancellationToken);
if (user == null)
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
if (request.FirstName != null) user.FirstName = request.FirstName;
if (request.LastName != null) user.LastName = request.LastName;
if (request.Email != null) user.Email = request.Email;
if (request.NationalCode != null) user.NationalCode = request.NationalCode;
if (request.BirthDate != null) user.BirthDate = request.BirthDate.ToDateTime();
await _context.SaveChangesAsync(context.CancellationToken);
return new Empty();
}
public override async Task<GetCustomerProfileResponse> GetCustomerProfile(GetCustomerProfileRequest request, ServerCallContext context)
{
var query = new GetCustomerProfileQuery { UserId = 0 };
var result = await _sender.Send(query, context.CancellationToken);
return new GetCustomerProfileResponse
{
Id = result.Id,
FirstName = result.FirstName,
LastName = result.LastName,
Mobile = result.Mobile,
Email = result.Email,
NationalCode = result.NationalCode,
AvatarPath = result.AvatarPath,
ParentId = result.ParentId,
ReferralCode = result.ReferralCode,
IsMobileVerified = result.IsMobileVerified,
MobileVerifiedAt = result.MobileVerifiedAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(result.MobileVerifiedAt.Value, DateTimeKind.Utc))
: null,
EmailNotifications = result.EmailNotifications,
SmsNotifications = result.SmsNotifications,
PushNotifications = result.PushNotifications,
BirthDate = result.BirthDate.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(result.BirthDate.Value, DateTimeKind.Utc))
: null,
FullName = result.FullName,
ProfileCompletionPercentage = result.ProfileCompletionPercentage
};
}
public override async Task<ChangeCustomerPasswordResponse> ChangeCustomerPassword(ChangeCustomerPasswordRequest request, ServerCallContext context)
{
if (request.NewPassword != request.ConfirmPassword)
{
return new ChangeCustomerPasswordResponse
{
Success = false,
Message = "رمز عبور جدید و تکرار آن یکسان نیستند"
};
}
if (request.NewPassword.Length < 6)
{
return new ChangeCustomerPasswordResponse
{
Success = false,
Message = "رمز عبور باید حداقل 6 کاراکتر باشد"
};
}
var userId = GetCurrentUserId();
var user = await _context.Users
.Where(u => u.Id == userId && !u.IsDeleted)
.FirstOrDefaultAsync(context.CancellationToken);
if (user == null)
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
// Verify current password
if (!string.IsNullOrEmpty(user.HashPassword))
{
if (!_hashService.VerifyPassword(request.CurrentPassword, user.HashPassword))
{
return new ChangeCustomerPasswordResponse
{
Success = false,
Message = "رمز عبور فعلی نادرست است"
};
}
}
// Hash and save new password
user.HashPassword = _hashService.HashPassword(request.NewPassword);
await _context.SaveChangesAsync(context.CancellationToken);
return new ChangeCustomerPasswordResponse
{
Success = true,
Message = "رمز عبور با موفقیت تغییر یافت"
};
}
public override async Task<GetCustomerReferralsResponse> GetCustomerReferrals(GetCustomerReferralsRequest request, ServerCallContext context)
{
var query = new GetCustomerReferralsQuery
{
UserId = 0,
PaginationState = request.PaginationState?.Adapt<AppModels.PaginationState>(),
StatusFilter = request.StatusFilter
};
var result = await _sender.Send(query, context.CancellationToken);
var response = new GetCustomerReferralsResponse
{
MetaData = new MetaData
{
CurrentPage = result.MetaData.CurrentPage,
TotalPage = result.MetaData.TotalPage,
PageSize = result.MetaData.PageSize,
TotalCount = result.MetaData.TotalCount,
HasPrevious = result.MetaData.HasPrevious,
HasNext = result.MetaData.HasNext
},
Stats = new CMSMicroservice.Protobuf.Protos.User.CustomerReferralStats
{
TotalReferrals = result.Stats.TotalReferrals,
ActiveReferrals = result.Stats.ActiveReferrals,
TotalCommissionEarned = result.Stats.TotalCommissionEarned,
ThisMonthCommission = result.Stats.ThisMonthCommission
}
};
foreach (var referral in result.Referrals)
{
response.Referrals.Add(new CMSMicroservice.Protobuf.Protos.User.CustomerReferralModel
{
Id = referral.Id,
FirstName = referral.FirstName,
LastName = referral.LastName,
Mobile = referral.Mobile,
JoinDate = Timestamp.FromDateTime(DateTime.SpecifyKind(referral.JoinDate, DateTimeKind.Utc)),
IsActive = referral.IsActive,
StatusMessage = referral.StatusMessage,
Level = referral.Level,
TotalCommission = referral.TotalCommission
});
}
return response;
}
public override async Task<UploadCustomerAvatarResponse> UploadCustomerAvatar(UploadCustomerAvatarRequest request, ServerCallContext context)
{
if (request.FileData == null || request.FileData.Length == 0)
{
return new UploadCustomerAvatarResponse
{
Success = false,
Message = "فایل انتخاب نشده است"
};
}
if (request.FileData.Length > 5 * 1024 * 1024) // 5MB limit
{
return new UploadCustomerAvatarResponse
{
Success = false,
Message = "حجم فایل نباید بیش از 5 مگابایت باشد"
};
}
var allowedTypes = new[] { "image/jpeg", "image/jpg", "image/png", "image/gif" };
if (!allowedTypes.Contains(request.FileMimeType?.ToLower()))
{
return new UploadCustomerAvatarResponse
{
Success = false,
Message = "فرمت فایل مجاز نیست. فقط JPG, PNG و GIF مجاز هستند"
};
}
var userId = GetCurrentUserId();
var user = await _context.Users
.Where(u => u.Id == userId && !u.IsDeleted)
.FirstOrDefaultAsync(context.CancellationToken);
if (user == null)
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
// Upload to local file manager
var fileBytes = request.FileData.ToByteArray();
var fileName = $"avatar_{userId}_{DateTime.UtcNow.Ticks}";
var mime = request.FileMimeType ?? "image/jpeg";
try
{
var result = await _fileManager.UploadImageAsync(
"Avatars", fileBytes, mime, fileName, context.CancellationToken);
// Update user avatar path in DB
user.AvatarPath = result.Main.Path;
await _context.SaveChangesAsync(context.CancellationToken);
return new UploadCustomerAvatarResponse
{
Success = true,
Message = "تصویر پروفایل با موفقیت آپلود شد",
AvatarUrl = result.Main.Path
};
}
catch (Exception ex)
{
return new UploadCustomerAvatarResponse
{
Success = false,
Message = "خطا در آپلود فایل. لطفاً مجدد تلاش کنید"
};
}
}
public override async Task<GetCustomerSettingsResponse> GetCustomerSettings(GetCustomerSettingsRequest request, ServerCallContext context)
{
var query = new GetCustomerSettingsQuery { UserId = 0 };
var result = await _sender.Send(query, context.CancellationToken);
return new GetCustomerSettingsResponse
{
EmailNotifications = result.EmailNotifications,
SmsNotifications = result.SmsNotifications,
PushNotifications = result.PushNotifications,
MarketingNotifications = result.MarketingNotifications,
PreferredLanguage = result.PreferredLanguage,
TimeZone = result.TimeZone,
TwoFactorAuthEnabled = result.TwoFactorAuthEnabled
};
}
public override async Task<Empty> UpdateCustomerSettings(UpdateCustomerSettingsRequest request, ServerCallContext context)
{
var userId = GetCurrentUserId();
var user = await _context.Users
.Where(u => u.Id == userId && !u.IsDeleted)
.FirstOrDefaultAsync(context.CancellationToken);
if (user == null)
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
user.EmailNotifications = request.EmailNotifications;
user.SmsNotifications = request.SmsNotifications;
user.PushNotifications = request.PushNotifications;
// MarketingNotifications, PreferredLanguage, TimeZone, TwoFactorAuth
// are stored at application level if needed in future
await _context.SaveChangesAsync(context.CancellationToken);
return new Empty();
}
// ============= Helper Methods =============
private long GetCurrentUserId()
{
if (long.TryParse(_currentUserService.UserId, out var userId) && userId > 0)
return userId;
throw new RpcException(new Status(StatusCode.Unauthenticated, "لطفاً وارد حساب کاربری خود شوید"));
}
}