8e5c7c5205
- ActivateClubMembership: Load Package, set First/Last fields, PackageId on Cycle+Pool - AcceptClubMembershipContract: Load Package, set First/Last fields, PackageId on Pool - CalculateWeeklyBalances: Load base package, use package.MaxBalancesPerLeg/MaxNetworkLevel, set PackageId on NetworkWeeklyBalance - ProcessUserPayouts: Load base package, use package.MaxNetworkLevel, set PackageId on UserCommissionPayout - CheckAndProcessDayaLoans: Load base package (IsBasePackage), replace DayaLoanAmount→package.Price, fix hardcoded p.Id==4 - CreateManualPayment: Load base package, replace BasePackageAmount→package.Price, use DiscountMultiplier - InitiateBasePackagePayment: Remove hardcoded BasePackageId=4, load from IsBasePackage, use package.Price/Id - VerifyBasePackagePayment: Load Package from order.PackageId, use package.Price+DiscountMultiplier - ChargeMagicWallet: Load user's package from current cycle, use package.MagicWalletMaxDeposit - VerifyMagicWalletCharge: Load user's package, use package.MagicWalletMultiplier/MagicWalletMaxDeposit - OrmCommissionCalculationStrategy: Load base package in both methods, set PackageId on Balance+Payout - ConfigurationService: Inject IApplicationDbContext, read package values from DB instead of SystemConstants - UserOrderService: Load user's package for EXIT trigger MagicWalletMaxDeposit check - UserWalletService: Load user's package for GetMagicWalletStatus display All 9 deprecated SystemConstants now have ZERO usages outside SystemConstants.cs Build: 0 errors, 521 warnings (all pre-existing)
316 lines
13 KiB
C#
316 lines
13 KiB
C#
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<CreateNewUserWalletResponse> CreateNewUserWallet(CreateNewUserWalletRequest request, ServerCallContext context)
|
|
{
|
|
return await _dispatchRequestToCQRS.Handle<CreateNewUserWalletRequest, CreateNewUserWalletCommand, CreateNewUserWalletResponse>(request, context);
|
|
}
|
|
public override async Task<Empty> UpdateUserWallet(UpdateUserWalletRequest request, ServerCallContext context)
|
|
{
|
|
return await _dispatchRequestToCQRS.Handle<UpdateUserWalletRequest, UpdateUserWalletCommand>(request, context);
|
|
}
|
|
public override async Task<Empty> DeleteUserWallet(DeleteUserWalletRequest request, ServerCallContext context)
|
|
{
|
|
return await _dispatchRequestToCQRS.Handle<DeleteUserWalletRequest, DeleteUserWalletCommand>(request, context);
|
|
}
|
|
public override async Task<GetUserWalletResponse> GetUserWallet(GetUserWalletRequest request, ServerCallContext context)
|
|
{
|
|
return await _dispatchRequestToCQRS.Handle<GetUserWalletRequest, GetUserWalletQuery, GetUserWalletResponse>(request, context);
|
|
}
|
|
public override async Task<GetAllUserWalletByFilterResponse> GetAllUserWalletByFilter(GetAllUserWalletByFilterRequest request, ServerCallContext context)
|
|
{
|
|
var response = await _dispatchRequestToCQRS.Handle<GetAllUserWalletByFilterRequest, GetAllUserWalletByFilterQuery, GetAllUserWalletByFilterResponse>(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<GetCustomerWalletResponse> 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<GetCustomerWalletChangeLogResponse> 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<Google.Protobuf.WellKnownTypes.Empty> 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<InitiateMagicChargeResponse> 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<InitiateDiscountChargeResponse> 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<GetMagicWalletStatusResponse> 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 currentCycle = await _context.ClubMembershipCycles
|
|
.FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, context.CancellationToken);
|
|
var package = currentCycle != null
|
|
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, context.CancellationToken)
|
|
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken);
|
|
var magicMaxDeposit = package?.MagicWalletMaxDeposit ?? 1_000_000_000;
|
|
|
|
var response = new GetMagicWalletStatusResponse
|
|
{
|
|
WalletMode = (int)wallet.WalletMode,
|
|
MagicTotalDeposited = wallet.MagicTotalDeposited,
|
|
MagicTotalCredited = wallet.MagicTotalCredited,
|
|
MagicMaxDeposit = magicMaxDeposit,
|
|
MagicRemainingDeposit = Math.Max(0, magicMaxDeposit - 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<GetCustomerWithdrawalsResponse> 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<GetCustomerWithdrawalSettingsResponse> 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
|
|
};
|
|
}
|
|
}
|