feat: Implement Magic Wallet functionality
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m36s
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.
This commit is contained in:
+36
-2
@@ -183,10 +183,9 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
return true;
|
||||
}
|
||||
|
||||
// فعالسازی مجدد
|
||||
// فعالسازی مجدد — ActivatedAt حفظ میشه (overwrite نمیشه)
|
||||
entity = existingMembership;
|
||||
entity.IsActive = true;
|
||||
entity.ActivatedAt = activationDate;
|
||||
entity.PurchaseMethod = user.PackagePurchaseMethod;
|
||||
|
||||
_context.ClubMemberships.Update(entity);
|
||||
@@ -199,6 +198,41 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
+12
-5
@@ -43,8 +43,14 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
|
||||
|
||||
// ⭐ دریافت همه کاربرانی که عضو فعال باشگاه هستند
|
||||
// بدون محدودیت زمانی - همه اعضای فعال کلاب باید کمیسیون بگیرند
|
||||
// ⚠️ Magic Wallet: کاربرهایی که در حالت جادویی هستند از کمیسیون خارج میشن
|
||||
var magicModeUserIds = await _context.UserWallets
|
||||
.Where(w => w.WalletMode == WalletMode.Magic)
|
||||
.Select(w => w.UserId)
|
||||
.ToHashSetAsync(cancellationToken);
|
||||
|
||||
var activeClubMemberUserIds = await _context.ClubMemberships
|
||||
.Where(c => c.IsActive)
|
||||
.Where(c => c.IsActive && !magicModeUserIds.Contains(c.UserId))
|
||||
.Select(c => c.UserId)
|
||||
.ToHashSetAsync(cancellationToken);
|
||||
|
||||
@@ -256,11 +262,12 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
|
||||
|
||||
var count = 0;
|
||||
|
||||
// اگر فرزند در این هفته فعال شده، 1 امتیاز
|
||||
var membership = await _context.ClubMemberships
|
||||
.FirstOrDefaultAsync(x => x.UserId == child.Id && x.IsActive, cancellationToken);
|
||||
// اگر فرزند در این هفته پکیج خریده (Cycle جدید ساخته شده)، 1 امتیاز
|
||||
// ⚠️ از ClubMembershipCycle.PackagePurchasedAt استفاده میکنیم (نه ActivatedAt که overwrite میشد)
|
||||
var currentCycle = await _context.ClubMembershipCycles
|
||||
.FirstOrDefaultAsync(x => x.UserId == child.Id && x.IsCurrentCycle, cancellationToken);
|
||||
|
||||
if (membership?.ActivatedAt >= startDate && membership?.ActivatedAt <= endDate)
|
||||
if (currentCycle?.PackagePurchasedAt >= startDate && currentCycle?.PackagePurchasedAt <= endDate)
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ public interface IApplicationDbContext
|
||||
DbSet<PublicMessage> PublicMessages { get; }
|
||||
DbSet<ClubMembership> ClubMemberships { get; }
|
||||
DbSet<ClubMembershipHistory> ClubMembershipHistories { get; }
|
||||
DbSet<ClubMembershipCycle> ClubMembershipCycles { get; }
|
||||
DbSet<ClubFeature> ClubFeatures { get; }
|
||||
DbSet<UserClubFeature> UserClubFeatures { get; }
|
||||
DbSet<NetworkWeeklyBalance> NetworkWeeklyBalances { get; }
|
||||
|
||||
@@ -6,14 +6,14 @@ namespace CMSMicroservice.Application.Common.Services;
|
||||
public static class VatCalculator
|
||||
{
|
||||
/// <summary>
|
||||
/// نرخ VAT ایران - 9 درصد
|
||||
/// نرخ VAT ایران - 10 درصد
|
||||
/// </summary>
|
||||
public const decimal VAT_RATE = 0.09m;
|
||||
public const decimal VAT_RATE = 0.10m;
|
||||
|
||||
/// <summary>
|
||||
/// نرخ VAT به صورت درصد (9)
|
||||
/// نرخ VAT به صورت درصد (10)
|
||||
/// </summary>
|
||||
public const int VAT_PERCENT = 9;
|
||||
public const int VAT_PERCENT = 10;
|
||||
|
||||
/// <summary>
|
||||
/// محاسبه VAT از مبلغ خالص
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet;
|
||||
|
||||
/// <summary>
|
||||
/// شروع شارژ کیفپول جادویی از طریق درگاه پرداخت
|
||||
/// مبلغ واریزی × 2.5 به Balance اعتبار داده میشود
|
||||
/// </summary>
|
||||
public class ChargeMagicWalletCommand : IRequest<PaymentInitiateResult>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه کاربر
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// مبلغ واریزی واقعی (ریال) — اعتبار = مبلغ × 2.5
|
||||
/// </summary>
|
||||
public long Amount { get; set; }
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Common;
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using CMSMicroservice.Domain.Entities.Payment;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet;
|
||||
|
||||
public class ChargeMagicWalletCommandHandler
|
||||
: IRequestHandler<ChargeMagicWalletCommand, PaymentInitiateResult>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<ChargeMagicWalletCommandHandler> _logger;
|
||||
|
||||
public ChargeMagicWalletCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
IConfiguration configuration,
|
||||
ILogger<ChargeMagicWalletCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_paymentGateway = paymentGateway;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<PaymentInitiateResult> Handle(
|
||||
ChargeMagicWalletCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Initiating magic wallet charge for UserId: {UserId}, Amount: {Amount}",
|
||||
request.UserId,
|
||||
request.Amount
|
||||
);
|
||||
|
||||
// 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. بررسی وجود کیفپول
|
||||
var wallet = await _context.UserWallets
|
||||
.FirstOrDefaultAsync(w => w.UserId == request.UserId, cancellationToken);
|
||||
|
||||
if (wallet == null)
|
||||
{
|
||||
_logger.LogError("Wallet not found for UserId: {UserId}", request.UserId);
|
||||
throw new NotFoundException("کیف پول کاربر یافت نشد");
|
||||
}
|
||||
|
||||
// 3. بررسی حالت جادویی
|
||||
if (wallet.WalletMode != WalletMode.Magic)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"User {UserId} wallet is not in Magic mode (current: {Mode})",
|
||||
request.UserId,
|
||||
wallet.WalletMode
|
||||
);
|
||||
throw new BadRequestException(
|
||||
"شارژ جادویی فقط در حالت کیفپول جادویی امکانپذیر است"
|
||||
);
|
||||
}
|
||||
|
||||
// 4. بررسی سقف واریزی (per-cycle)
|
||||
var remainingDeposit = SystemConstants.MagicWalletMaxDeposit - wallet.MagicTotalDeposited;
|
||||
|
||||
if (remainingDeposit <= 0)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"User {UserId} has reached magic deposit cap. TotalDeposited: {TotalDeposited}",
|
||||
request.UserId,
|
||||
wallet.MagicTotalDeposited
|
||||
);
|
||||
throw new BadRequestException(
|
||||
"سقف شارژ جادویی در این دور پر شده است"
|
||||
);
|
||||
}
|
||||
|
||||
if (request.Amount > remainingDeposit)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"User {UserId} amount {Amount} exceeds remaining deposit cap {Remaining}",
|
||||
request.UserId,
|
||||
request.Amount,
|
||||
remainingDeposit
|
||||
);
|
||||
throw new BadRequestException(
|
||||
$"مبلغ واریزی بیش از سقف باقیمانده است. حداکثر مبلغ قابل واریز: {remainingDeposit:N0} ریال"
|
||||
);
|
||||
}
|
||||
|
||||
// 5. ایجاد درخواست پرداخت
|
||||
var cmsBaseUrl = _configuration["CmsBaseUrl"] ?? "https://localhost:32846";
|
||||
var callbackUrl = $"{cmsBaseUrl}/api/wallet/verify-magic-charge";
|
||||
|
||||
var paymentRequest = new PaymentRequest
|
||||
{
|
||||
Amount = request.Amount,
|
||||
UserId = user.Id,
|
||||
Mobile = user.Mobile ?? "",
|
||||
CallbackUrl = callbackUrl,
|
||||
Description = $"شارژ کیفپول جادویی - کاربر {user.Id}"
|
||||
};
|
||||
|
||||
var paymentResult = await _paymentGateway.InitiatePaymentAsync(paymentRequest);
|
||||
|
||||
if (!paymentResult.IsSuccess)
|
||||
{
|
||||
_logger.LogError(
|
||||
"Payment gateway failed for magic charge UserId {UserId}: {ErrorMessage}",
|
||||
user.Id,
|
||||
paymentResult.ErrorMessage
|
||||
);
|
||||
|
||||
throw new Exception($"خطا در ارتباط با درگاه پرداخت: {paymentResult.ErrorMessage}");
|
||||
}
|
||||
|
||||
// 6. ثبت PaymentTransaction
|
||||
var paymentTx = new PaymentTransaction
|
||||
{
|
||||
GatewayProvider = _configuration["PaymentProvider"] ?? "zarinpal",
|
||||
MerchantId = _configuration["ZarinPal:MerchantId"] ?? "",
|
||||
Amount = request.Amount,
|
||||
CallbackUrl = callbackUrl,
|
||||
Description = $"شارژ کیفپول جادویی - کاربر {user.Id}",
|
||||
Mobile = user.Mobile,
|
||||
UserId = user.Id,
|
||||
RequestStatusCode = 100,
|
||||
RequestStatusMessage = "Success",
|
||||
Authority = paymentResult.RefId,
|
||||
PaymentStatus = false
|
||||
};
|
||||
|
||||
_context.PaymentTransactions.Add(paymentTx);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Magic wallet charge initiated. UserId: {UserId}, Amount: {Amount}, " +
|
||||
"RemainingCap: {Remaining}, RefId: {RefId}, PaymentTxId: {PaymentTxId}",
|
||||
user.Id,
|
||||
request.Amount,
|
||||
remainingDeposit - request.Amount,
|
||||
paymentResult.RefId,
|
||||
paymentTx.Id
|
||||
);
|
||||
|
||||
return paymentResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Error in ChargeMagicWalletCommand for UserId: {UserId}",
|
||||
request.UserId
|
||||
);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet;
|
||||
|
||||
public class ChargeMagicWalletCommandValidator : AbstractValidator<ChargeMagicWalletCommand>
|
||||
{
|
||||
public ChargeMagicWalletCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر باید بزرگتر از صفر باشد");
|
||||
|
||||
RuleFor(x => x.Amount)
|
||||
.GreaterThanOrEqualTo(100_000)
|
||||
.WithMessage("حداقل مبلغ شارژ جادویی ۱۰,۰۰۰ تومان است")
|
||||
.LessThanOrEqualTo(1_000_000_000)
|
||||
.WithMessage("حداکثر مبلغ شارژ جادویی ۱۰۰,۰۰۰,۰۰۰ تومان است");
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.WalletCQ.Commands.VerifyMagicWalletCharge;
|
||||
|
||||
/// <summary>
|
||||
/// تأیید شارژ کیفپول جادویی — بعد از بازگشت از درگاه پرداخت
|
||||
/// </summary>
|
||||
public class VerifyMagicWalletChargeCommand : IRequest<bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// کد Authority از درگاه
|
||||
/// </summary>
|
||||
public string Authority { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// وضعیت برگشتی از درگاه (OK / NOK)
|
||||
/// </summary>
|
||||
public string Status { get; set; } = string.Empty;
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Common;
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using CMSMicroservice.Domain.Entities.Payment;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CMSMicroservice.Application.WalletCQ.Commands.VerifyMagicWalletCharge;
|
||||
|
||||
public class VerifyMagicWalletChargeCommandHandler
|
||||
: IRequestHandler<VerifyMagicWalletChargeCommand, bool>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
private readonly ILogger<VerifyMagicWalletChargeCommandHandler> _logger;
|
||||
|
||||
public VerifyMagicWalletChargeCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IPaymentGatewayService paymentGateway,
|
||||
ILogger<VerifyMagicWalletChargeCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_paymentGateway = paymentGateway;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(
|
||||
VerifyMagicWalletChargeCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Verifying magic wallet charge. Authority: {Authority}, Status: {Status}",
|
||||
request.Authority,
|
||||
request.Status
|
||||
);
|
||||
|
||||
// 1. پیدا کردن PaymentTransaction از Authority
|
||||
var paymentTx = await _context.PaymentTransactions
|
||||
.FirstOrDefaultAsync(pt => pt.Authority == request.Authority, cancellationToken);
|
||||
|
||||
if (paymentTx == null)
|
||||
{
|
||||
_logger.LogError("PaymentTransaction not found for Authority: {Authority}", request.Authority);
|
||||
throw new NotFoundException("تراکنش پرداخت یافت نشد");
|
||||
}
|
||||
|
||||
if (paymentTx.PaymentStatus)
|
||||
{
|
||||
_logger.LogWarning("PaymentTransaction already verified: {Authority}", request.Authority);
|
||||
return true; // قبلاً تأیید شده
|
||||
}
|
||||
|
||||
var userId = paymentTx.UserId
|
||||
?? throw new BadRequestException("شناسه کاربر در تراکنش پرداخت یافت نشد");
|
||||
var depositAmount = paymentTx.Amount; // مبلغ واقعی واریزی (ریال)
|
||||
|
||||
// 2. بررسی وضعیت برگشتی از درگاه
|
||||
if (!string.Equals(request.Status, "OK", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Magic charge cancelled by user. UserId: {UserId}, Authority: {Authority}",
|
||||
userId, request.Authority
|
||||
);
|
||||
|
||||
paymentTx.PaymentStatus = false;
|
||||
paymentTx.VerificationStatusMessage = "پرداخت توسط کاربر لغو شد";
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
throw new BadRequestException("پرداخت توسط کاربر لغو شد");
|
||||
}
|
||||
|
||||
// 3. Verify با درگاه (زرینپال نیاز به مبلغ دارد)
|
||||
var amountInToman = depositAmount / 10m;
|
||||
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
|
||||
request.Authority,
|
||||
request.Status,
|
||||
amountInToman,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
// آپدیت PaymentTransaction
|
||||
paymentTx.PaymentStatus = verifyResult.IsSuccess;
|
||||
paymentTx.VerificationStatusCode = verifyResult.VerificationCode;
|
||||
paymentTx.VerificationStatusMessage = verifyResult.Message;
|
||||
paymentTx.CardPan = verifyResult.CardPan;
|
||||
paymentTx.CardHash = verifyResult.CardHash;
|
||||
paymentTx.RefId = verifyResult.TrackingCode;
|
||||
|
||||
if (!verifyResult.IsSuccess)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Magic wallet charge verification failed for UserId {UserId}: {Message}",
|
||||
userId,
|
||||
verifyResult.Message
|
||||
);
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
throw new BadRequestException($"تراکنش ناموفق: {verifyResult.Message}");
|
||||
}
|
||||
|
||||
// 4. بررسی کیفپول و حالت جادویی
|
||||
var wallet = await _context.UserWallets
|
||||
.FirstOrDefaultAsync(w => w.UserId == userId, cancellationToken);
|
||||
|
||||
if (wallet == null)
|
||||
{
|
||||
_logger.LogError("Wallet not found for UserId: {UserId}", userId);
|
||||
throw new NotFoundException("کیف پول کاربر یافت نشد");
|
||||
}
|
||||
|
||||
if (wallet.WalletMode != WalletMode.Magic)
|
||||
{
|
||||
_logger.LogError(
|
||||
"Wallet is not in Magic mode during verify. UserId: {UserId}, Mode: {Mode}",
|
||||
userId, wallet.WalletMode
|
||||
);
|
||||
throw new BadRequestException("کیفپول در حالت جادویی نیست");
|
||||
}
|
||||
|
||||
// 5. محاسبه اعتبار ×2.5
|
||||
var creditAmount = (long)(depositAmount * SystemConstants.MagicWalletMultiplier); // مبلغ × 2.5
|
||||
var bonusAmount = creditAmount - depositAmount; // بونوس = مبلغ × 1.5
|
||||
|
||||
// 6. ثبت تراکنش واریز واقعی (MagicWalletDeposit)
|
||||
var depositTransaction = new Transaction
|
||||
{
|
||||
Amount = depositAmount,
|
||||
Description = $"شارژ کیفپول جادویی - واریز واقعی - کاربر {userId}",
|
||||
PaymentStatus = PaymentStatus.Success,
|
||||
PaymentDate = DateTime.UtcNow,
|
||||
RefId = verifyResult.RefId,
|
||||
Type = TransactionType.MagicWalletDeposit
|
||||
};
|
||||
|
||||
_context.Transactions.Add(depositTransaction);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 7. ثبت تراکنش بونوس (MagicWalletBonus)
|
||||
var bonusTransaction = new Transaction
|
||||
{
|
||||
Amount = bonusAmount,
|
||||
Description = $"شارژ کیفپول جادویی - بونوس ×{SystemConstants.MagicWalletMultiplier - 1} - کاربر {userId}",
|
||||
PaymentStatus = PaymentStatus.Success,
|
||||
PaymentDate = DateTime.UtcNow,
|
||||
RefId = $"MAGIC_BONUS_{depositTransaction.Id}",
|
||||
Type = TransactionType.MagicWalletBonus
|
||||
};
|
||||
|
||||
_context.Transactions.Add(bonusTransaction);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 8. شارژ Balance و آپدیت شمارندهها
|
||||
wallet.Balance += creditAmount;
|
||||
wallet.MagicTotalDeposited += depositAmount;
|
||||
wallet.MagicTotalCredited += creditAmount;
|
||||
|
||||
// 9. ثبت WalletChangeLog (اجباری)
|
||||
var walletLog = new UserWalletChangeLog
|
||||
{
|
||||
WalletId = wallet.Id,
|
||||
CurrentBalance = wallet.Balance,
|
||||
ChangeValue = creditAmount,
|
||||
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||
ChangeNerworkValue = 0,
|
||||
CurrentDiscountBalance = wallet.DiscountBalance,
|
||||
ChangeDiscountValue = 0,
|
||||
IsIncrease = true,
|
||||
RefrenceId = depositTransaction.Id
|
||||
};
|
||||
|
||||
_context.UserWalletChangeLogs.Add(walletLog);
|
||||
|
||||
// 10. لینک PaymentTransaction به Transaction داخلی
|
||||
paymentTx.TransactionId = depositTransaction.Id;
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Magic wallet charged successfully. UserId: {UserId}, " +
|
||||
"Deposit: {Deposit}, Credit: {Credit} (×{Multiplier}), Bonus: {Bonus}, " +
|
||||
"TotalDeposited: {TotalDeposited}/{MaxDeposit}, NewBalance: {NewBalance}",
|
||||
userId,
|
||||
depositAmount,
|
||||
creditAmount,
|
||||
SystemConstants.MagicWalletMultiplier,
|
||||
bonusAmount,
|
||||
wallet.MagicTotalDeposited,
|
||||
SystemConstants.MagicWalletMaxDeposit,
|
||||
wallet.Balance
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Error in VerifyMagicWalletChargeCommand. Authority: {Authority}",
|
||||
request.Authority
|
||||
);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user