using CMSMicroservice.Protobuf.Protos.UserWallet; using CMSMicroservice.WebApi.Common.Services; using CMSMicroservice.Application.UserWalletCQ.Commands.CreateNewUserWallet; using CMSMicroservice.Application.UserWalletCQ.Commands.UpdateUserWallet; using CMSMicroservice.Application.UserWalletCQ.Commands.DeleteUserWallet; using CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet; using CMSMicroservice.Application.WalletCQ.Commands.ChargeDiscountWallet; using CMSMicroservice.Application.UserWalletCQ.Queries.GetUserWallet; using CMSMicroservice.Application.UserWalletCQ.Queries.GetAllUserWalletByFilter; using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog; using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals; using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Common; using CMSMicroservice.Domain.Enums; using Grpc.Core; using Google.Protobuf.WellKnownTypes; using Microsoft.EntityFrameworkCore; using System.Linq; namespace CMSMicroservice.WebApi.Services; public class UserWalletService : UserWalletContract.UserWalletContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; private readonly ISender _sender; private readonly IApplicationDbContext _context; private readonly ICurrentUserService _currentUserService; public UserWalletService( IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender, IApplicationDbContext context, ICurrentUserService currentUserService) { _dispatchRequestToCQRS = dispatchRequestToCQRS; _sender = sender; _context = context; _currentUserService = currentUserService; } public override async Task CreateNewUserWallet(CreateNewUserWalletRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task UpdateUserWallet(UpdateUserWalletRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task DeleteUserWallet(DeleteUserWalletRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task GetUserWallet(GetUserWalletRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task GetAllUserWalletByFilter(GetAllUserWalletByFilterRequest request, ServerCallContext context) { var response = await _dispatchRequestToCQRS.Handle(request, context); // Enrich response with user names if (response?.Models != null && response.Models.Any()) { var userIds = response.Models.Select(m => m.UserId).Distinct().ToList(); var users = await _context.Users .AsNoTracking() .Where(u => userIds.Contains(u.Id)) .Select(u => new { u.Id, u.FirstName, u.LastName }) .ToDictionaryAsync(u => u.Id, context.CancellationToken); foreach (var model in response.Models) { if (users.TryGetValue(model.UserId, out var user)) { model.UserName = $"{user.FirstName} {user.LastName}".Trim(); } } } return response; } // ============= Customer-specific Methods ============= public override async Task GetCustomerWallet(Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context) { // Customer endpoint: resolve userId from JWT if (!long.TryParse(_currentUserService.UserId, out var userId) || userId <= 0) throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است")); var walletQuery = new GetUserWalletQuery { Id = userId }; var wallet = await _sender.Send(walletQuery, context.CancellationToken); var walletEntity = await _context.UserWallets .FirstOrDefaultAsync(w => w.UserId == userId, context.CancellationToken); return new GetCustomerWalletResponse { Balance = wallet.Balance, NetworkBalance = wallet.NetworkBalance, DiscountBalance = wallet.DiscountBalance, WalletMode = (int)(walletEntity?.WalletMode ?? WalletMode.Normal) }; } public override async Task GetCustomerWalletChangeLog(GetCustomerWalletChangeLogRequest request, ServerCallContext context) { var query = new GetCustomerWalletChangeLogQuery { ReferenceId = request.ReferenceId, IsIncrease = request.IsIncrease }; var changeLogs = await _sender.Send(query, context.CancellationToken); var response = new GetCustomerWalletChangeLogResponse { MetaData = new CMSMicroservice.Protobuf.Protos.MetaData { CurrentPage = 1, TotalPage = 1, PageSize = changeLogs.Count, TotalCount = changeLogs.Count, HasPrevious = false, HasNext = false } }; foreach (var log in changeLogs) { response.Models.Add(new CustomerWalletChangeLogModel { CurrentBalance = log.CurrentBalance, ChangeValue = log.ChangeValue, CurrentNetworkBalance = log.CurrentNetworkBalance, ChangeNerworkValue = log.ChangeNerworkValue, CurrentDiscountBalance = log.CurrentDiscountBalance, ChangeDiscountValue = log.ChangeDiscountValue, IsIncrease = log.IsIncrease, RefrenceId = log.RefrenceId, CreatedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.SpecifyKind(log.Created, DateTimeKind.Utc)) }); } return response; } public override async Task CustomerWithdrawBalance(CustomerWithdrawBalanceRequest request, ServerCallContext context) { var userId = GetCurrentUserId(); // Find the commission payout record var payout = await _context.UserCommissionPayouts .Where(p => p.Id == request.PayoutId && p.UserId == userId && !p.IsDeleted) .FirstOrDefaultAsync(context.CancellationToken); if (payout == null) throw new RpcException(new Status(StatusCode.NotFound, "رکورد پرداخت کمیسیون یافت نشد")); // Validate status - can only withdraw if already paid to wallet if (payout.Status != Domain.Enums.CommissionPayoutStatus.Paid) throw new RpcException(new Status(StatusCode.FailedPrecondition, "فقط کمیسیون‌های واریز شده به کیف پول قابل برداشت هستند")); // Update payout with withdrawal info payout.WithdrawalMethod = (Domain.Enums.WithdrawalMethod)request.WithdrawalMethod; payout.IbanNumber = request.IbanNumber; payout.Status = Domain.Enums.CommissionPayoutStatus.WithdrawRequested; await _context.SaveChangesAsync(context.CancellationToken); return new Google.Protobuf.WellKnownTypes.Empty(); } // ============= Magic Wallet Methods ============= public override async Task InitiateMagicCharge( InitiateMagicChargeRequest request, ServerCallContext context) { var userId = GetCurrentUserId(); var result = await _sender.Send(new ChargeMagicWalletCommand { UserId = userId, Amount = request.Amount }, context.CancellationToken); return new InitiateMagicChargeResponse { IsSuccess = result.IsSuccess, GatewayUrl = result.GatewayUrl ?? "", ErrorMessage = result.ErrorMessage ?? "" }; } // ============= Discount Wallet Methods ============= public override async Task InitiateDiscountCharge( InitiateDiscountChargeRequest request, ServerCallContext context) { var userId = GetCurrentUserId(); var result = await _sender.Send(new ChargeDiscountWalletCommand { UserId = userId, Amount = request.Amount }, context.CancellationToken); return new InitiateDiscountChargeResponse { IsSuccess = result.IsSuccess, GatewayUrl = result.GatewayUrl ?? "", ErrorMessage = result.ErrorMessage ?? "" }; } public override async Task GetMagicWalletStatus( Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context) { var userId = GetCurrentUserId(); var wallet = await _context.UserWallets .FirstOrDefaultAsync(w => w.UserId == userId, context.CancellationToken); if (wallet == null) throw new RpcException(new Status(StatusCode.NotFound, "کیف پول یافت نشد")); var cycleCount = await _context.ClubMembershipCycles .CountAsync(c => c.UserId == userId, context.CancellationToken); var response = new GetMagicWalletStatusResponse { WalletMode = (int)wallet.WalletMode, MagicTotalDeposited = wallet.MagicTotalDeposited, MagicTotalCredited = wallet.MagicTotalCredited, MagicMaxDeposit = SystemConstants.MagicWalletMaxDeposit, MagicRemainingDeposit = Math.Max(0, SystemConstants.MagicWalletMaxDeposit - wallet.MagicTotalDeposited), Balance = wallet.Balance, PurchaseCycleCount = cycleCount }; if (wallet.MagicActivatedAt.HasValue) { response.MagicActivatedAt = Timestamp.FromDateTime( DateTime.SpecifyKind(wallet.MagicActivatedAt.Value, DateTimeKind.Utc)); } return response; } private long GetCurrentUserId() { if (long.TryParse(_currentUserService.UserId, out var userId) && userId > 0) return userId; throw new RpcException(new Status(StatusCode.Unauthenticated, "لطفاً وارد حساب کاربری خود شوید")); } public override async Task GetCustomerWithdrawals(GetCustomerWithdrawalsRequest request, ServerCallContext context) { var query = new GetCustomerWithdrawalsQuery { Status = request.Status }; var withdrawals = await _sender.Send(query, context.CancellationToken); var response = new GetCustomerWithdrawalsResponse { MetaData = new CMSMicroservice.Protobuf.Protos.MetaData { CurrentPage = 1, TotalPage = 1, PageSize = withdrawals.Count, TotalCount = withdrawals.Count, HasPrevious = false, HasNext = false } }; foreach (var withdrawal in withdrawals) { response.Models.Add(new CustomerWithdrawalModel { Id = withdrawal.Id, WeekDefinitionId = withdrawal.WeekDefinitionId, WeekDisplayName = withdrawal.WeekDisplayName, TotalAmount = withdrawal.TotalAmount, Status = withdrawal.Status, WithdrawalMethod = withdrawal.WithdrawalMethod, IbanNumber = withdrawal.IbanNumber, Created = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.SpecifyKind(withdrawal.Created, DateTimeKind.Utc)) }); } return response; } public override async Task GetCustomerWithdrawalSettings(Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context) { var query = new GetCustomerWithdrawalSettingsQuery(); var settings = await _sender.Send(query, context.CancellationToken); return new GetCustomerWithdrawalSettingsResponse { MinWithdrawalAmount = settings.MinWithdrawalAmount }; } }