2a569a024f
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m36s
- Added ClubMembershipCycle entity and DbSet to IApplicationDbContext. - Updated VAT rate from 9% to 10% in VatCalculator and related areas. - Introduced Magic Wallet settings in SystemConstants. - Enhanced UserWallet entity to support Magic Wallet features. - Updated TransactionType enum to include Magic Wallet transactions. - Configured UserWallet to handle Magic Wallet properties in ApplicationDbContext. - Implemented OrmCommissionCalculationStrategy to exclude Magic Wallet users from commission calculations. - Added Protobuf definitions for Magic Wallet methods and responses. - Created PaymentCallbackController endpoint for handling Magic Wallet charge callbacks. - Updated UserOrderService to manage Magic Wallet state transitions. - Developed UserWalletService to support Magic Wallet operations. - Created ChargeMagicWalletCommand and its handler for initiating Magic Wallet charges. - Implemented VerifyMagicWalletChargeCommand and handler for payment verification. - Added validation for ChargeMagicWalletCommand. - Established ClubMembershipCycle configuration for EF Core. - Introduced WalletMode enum to differentiate between Normal and Magic modes.
363 lines
14 KiB
C#
363 lines
14 KiB
C#
using CMSMicroservice.Application.Common.Exceptions;
|
|
using CMSMicroservice.Application.Common.Interfaces;
|
|
using CMSMicroservice.Application.Common.Models;
|
|
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 MediatR;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using System.Globalization;
|
|
|
|
namespace CMSMicroservice.Application.ClubMembershipCQ.Commands.ActivateClubMembership;
|
|
|
|
public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClubMembershipCommand, bool>
|
|
{
|
|
private readonly IApplicationDbContext _context;
|
|
private readonly ICurrentUserService _currentUser;
|
|
private readonly IWeekDefinitionRepository _weekRepository;
|
|
private readonly ILogger<ActivateClubMembershipCommandHandler> _logger;
|
|
|
|
public ActivateClubMembershipCommandHandler(
|
|
IApplicationDbContext context,
|
|
ICurrentUserService currentUser,
|
|
IWeekDefinitionRepository weekRepository,
|
|
ILogger<ActivateClubMembershipCommandHandler> logger)
|
|
{
|
|
_context = context;
|
|
_currentUser = currentUser;
|
|
_weekRepository = weekRepository;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<bool> Handle(
|
|
ActivateClubMembershipCommand request,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation(
|
|
"Activating club membership for UserId: {UserId}",
|
|
request.UserId
|
|
);
|
|
|
|
// 1. بررسی کاربر
|
|
var user = await _context.Users
|
|
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
|
|
|
|
if (user == null)
|
|
{
|
|
_logger.LogWarning("User not found: {UserId}", request.UserId);
|
|
throw new NotFoundException(nameof(User), request.UserId);
|
|
}
|
|
|
|
// 2. بررسی اینکه پکیج خریده باشد
|
|
if (user.PackagePurchaseMethod == PackagePurchaseMethod.None)
|
|
{
|
|
_logger.LogWarning(
|
|
"User {UserId} has not purchased golden package yet",
|
|
request.UserId
|
|
);
|
|
throw new BadRequestException(
|
|
"برای فعالسازی باشگاه مشتریان ابتدا باید پکیج طلایی خریداری کنید"
|
|
);
|
|
}
|
|
|
|
// 3. بررسی موجودی کیف پول
|
|
var wallet = await _context.UserWallets
|
|
.FirstOrDefaultAsync(w => w.UserId == user.Id, cancellationToken);
|
|
|
|
if (wallet == null)
|
|
{
|
|
_logger.LogError("Wallet not found for UserId: {UserId}", request.UserId);
|
|
throw new NotFoundException("کیف پول کاربر یافت نشد");
|
|
}
|
|
|
|
if (wallet.Balance < SystemConstants.BasePackageAmount)
|
|
{
|
|
_logger.LogWarning(
|
|
"User {UserId} has insufficient balance: {Balance}",
|
|
request.UserId,
|
|
wallet.Balance
|
|
);
|
|
throw new BadRequestException(
|
|
$"برای فعالسازی باشگاه مشتریان باید حداقل {SystemConstants.BasePackageAmount:N0} ریال موجودی اصلی داشته باشید"
|
|
);
|
|
}
|
|
|
|
// 4. پیدا کردن UserOrder با PackageId
|
|
var packageOrder = await _context.UserOrders
|
|
.Include(o => o.Transaction)
|
|
.Where(o =>
|
|
o.UserId == user.Id &&
|
|
o.PackageId != null &&
|
|
o.PaymentStatus == PaymentStatus.Success)
|
|
.OrderByDescending(o => o.Created)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
if (packageOrder == null)
|
|
{
|
|
_logger.LogWarning(
|
|
"No successful package order found for UserId: {UserId}",
|
|
request.UserId
|
|
);
|
|
throw new NotFoundException("سفارش پکیج طلایی یافت نشد");
|
|
}
|
|
|
|
// 5. بررسی Transaction
|
|
if (packageOrder.Transaction == null)
|
|
{
|
|
_logger.LogError(
|
|
"Transaction not found for OrderId: {OrderId}",
|
|
packageOrder.Id
|
|
);
|
|
throw new NotFoundException("تراکنش مربوط به سفارش یافت نشد");
|
|
}
|
|
|
|
var transaction = packageOrder.Transaction;
|
|
|
|
if (transaction.Type != TransactionType.DepositIpg &&
|
|
transaction.Type != TransactionType.DepositExternal1)
|
|
{
|
|
_logger.LogWarning(
|
|
"Invalid transaction type for OrderId {OrderId}: {Type}",
|
|
packageOrder.Id,
|
|
transaction.Type
|
|
);
|
|
throw new BadRequestException(
|
|
"تراکنش معتبر برای فعالسازی باشگاه یافت نشد"
|
|
);
|
|
}
|
|
|
|
// 6. بررسی عضویت فعلی
|
|
var existingMembership = await _context.ClubMemberships
|
|
.FirstOrDefaultAsync(c => c.UserId == user.Id, cancellationToken);
|
|
|
|
// 6.1. دریافت مبلغ هدیه و هزینه فعالسازی از SystemConstants
|
|
long giftValue = SystemConstants.ClubMembershipGiftValue;
|
|
long activationFeeValue = SystemConstants.ClubActivationFee;
|
|
|
|
_logger.LogInformation(
|
|
"Using Club.MembershipGiftValue: {GiftValue}, Club.ActivationFee: {ActivationFee}",
|
|
giftValue, activationFeeValue
|
|
);
|
|
|
|
ClubMembership entity;
|
|
bool isNewMembership = existingMembership == null;
|
|
var activationDate = DateTime.Now;
|
|
|
|
if (isNewMembership)
|
|
{
|
|
// ایجاد عضویت جدید
|
|
entity = new ClubMembership
|
|
{
|
|
UserId = user.Id,
|
|
IsActive = true,
|
|
ActivatedAt = activationDate,
|
|
InitialContribution =activationFeeValue,
|
|
GiftValue = giftValue, // مقدار از تنظیمات
|
|
TotalEarned = 0,
|
|
PurchaseMethod = user.PackagePurchaseMethod
|
|
};
|
|
|
|
_context.ClubMemberships.Add(entity);
|
|
|
|
_logger.LogInformation(
|
|
"Created new club membership for UserId {UserId} via {Method}, GiftValue: {GiftValue}",
|
|
user.Id,
|
|
user.PackagePurchaseMethod,
|
|
giftValue
|
|
);
|
|
}
|
|
else
|
|
{
|
|
if (existingMembership.IsActive)
|
|
{
|
|
_logger.LogInformation(
|
|
"User {UserId} is already an active club member",
|
|
user.Id
|
|
);
|
|
return true;
|
|
}
|
|
|
|
// فعالسازی مجدد — ActivatedAt حفظ میشه (overwrite نمیشه)
|
|
entity = existingMembership;
|
|
entity.IsActive = true;
|
|
entity.PurchaseMethod = user.PackagePurchaseMethod;
|
|
|
|
_context.ClubMemberships.Update(entity);
|
|
|
|
_logger.LogInformation(
|
|
"Reactivated club membership for UserId {UserId}",
|
|
user.Id
|
|
);
|
|
}
|
|
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
|
|
// 6.5. ایجاد ClubMembershipCycle — هر خرید پکیج یک دور جدید
|
|
var previousCycles = await _context.ClubMembershipCycles
|
|
.Where(c => c.UserId == user.Id && c.IsCurrentCycle)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
foreach (var prevCycle in previousCycles)
|
|
{
|
|
prevCycle.IsCurrentCycle = false;
|
|
}
|
|
|
|
var maxCycleNumber = await _context.ClubMembershipCycles
|
|
.Where(c => c.ClubMembershipId == entity.Id)
|
|
.MaxAsync(c => (int?)c.CycleNumber, cancellationToken) ?? 0;
|
|
|
|
var newCycle = new ClubMembershipCycle
|
|
{
|
|
UserId = user.Id,
|
|
ClubMembershipId = entity.Id,
|
|
CycleNumber = maxCycleNumber + 1,
|
|
PackagePurchasedAt = activationDate,
|
|
PurchaseMethod = user.PackagePurchaseMethod,
|
|
PackageAmount = SystemConstants.BasePackageAmount,
|
|
IsCurrentCycle = true
|
|
};
|
|
|
|
_context.ClubMembershipCycles.Add(newCycle);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
|
|
_logger.LogInformation(
|
|
"Created ClubMembershipCycle #{CycleNumber} for UserId {UserId}, MembershipId {MembershipId}",
|
|
newCycle.CycleNumber,
|
|
user.Id,
|
|
entity.Id
|
|
);
|
|
|
|
// 7. ثبت تاریخچه
|
|
var history = new ClubMembershipHistory
|
|
{
|
|
ClubMembershipId = entity.Id,
|
|
UserId = entity.UserId,
|
|
OldIsActive = !isNewMembership && !existingMembership!.IsActive,
|
|
NewIsActive = true,
|
|
Action = ClubMembershipAction.Activated,
|
|
Reason = isNewMembership
|
|
? $"Initial activation via {user.PackagePurchaseMethod}"
|
|
: $"Reactivated via {user.PackagePurchaseMethod}",
|
|
PerformedBy = _currentUser.GetPerformedBy()
|
|
};
|
|
|
|
_context.ClubMembershipHistories.Add(history);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
|
|
// ⭐ 8. اضافه کردن مبلغ به Pool هفته جاری
|
|
var currentWeekDefinitionId = GetCurrentWeekDefinitionId();
|
|
var weeklyPool = await _context.WeeklyCommissionPools
|
|
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId, cancellationToken);
|
|
|
|
if (weeklyPool == null)
|
|
{
|
|
// ایجاد Pool جدید برای این هفته
|
|
weeklyPool = new WeeklyCommissionPool
|
|
{
|
|
WeekDefinitionId = currentWeekDefinitionId,
|
|
TotalPoolAmount = activationFeeValue, // مبلغ هدیه به Pool اضافه میشه
|
|
TotalBalances = 0, // در CalculateWeeklyBalances محاسبه میشه
|
|
ValuePerBalance = 0, // در CalculateWeeklyCommissionPool محاسبه میشه
|
|
IsCalculated = false,
|
|
CalculatedAt = null
|
|
};
|
|
|
|
await _context.WeeklyCommissionPools.AddAsync(weeklyPool, cancellationToken);
|
|
|
|
_logger.LogInformation(
|
|
"Created new WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId} with initial amount: {Amount}",
|
|
currentWeekDefinitionId,
|
|
giftValue
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// اضافه کردن به Pool موجود
|
|
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);
|
|
|
|
// 9. اضافه کردن ویژگیهای باشگاه برای کاربر (فقط برای عضویت جدید)
|
|
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 = entity.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
|
|
);
|
|
}
|
|
else
|
|
{
|
|
_logger.LogWarning(
|
|
"No club features found to grant to UserId {UserId}",
|
|
user.Id
|
|
);
|
|
}
|
|
}
|
|
|
|
_logger.LogInformation(
|
|
"Club membership activated successfully. UserId: {UserId}, MembershipId: {MembershipId}",
|
|
user.Id,
|
|
entity.Id
|
|
);
|
|
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(
|
|
ex,
|
|
"Error in ActivateClubMembershipCommand for UserId: {UserId}",
|
|
request.UserId
|
|
);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// دریافت شناسه تعریف هفته جاری
|
|
/// </summary>
|
|
private long GetCurrentWeekDefinitionId()
|
|
{
|
|
var week = _weekRepository.GetCurrentWeek();
|
|
if (week == null)
|
|
{
|
|
throw new InvalidOperationException("هفته جاری در سیستم تعریف نشده است");
|
|
}
|
|
return week.Id;
|
|
}
|
|
}
|