F2-F7: نوتیفیکیشن‌ها+نام‌پکیج، ستون پکیج در CSV، مقادیر داینامیک کیف‌پول جادویی، ولیدیتور SystemConstants
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m29s

F2: اضافه شدن packageName به SmsTemplates، IUserNotificationService، UserNotificationService
    - DayaLoan، ClubActivated، CommissionDeposited حالا نام پکیج را نشان می‌دهند
F4: اضافه شدن ستون پکیج به CSV خروجی‌ها
    - commission.proto: package_name در WithdrawalRequestModel
    - manualpayment.proto: package_name در ManualPaymentModel
    - کوئری‌هندلرها UserPackagePurchases لوکاپ اضافه شد
F6: مقادیر داینامیک کیف‌پول جادویی از پکیج
    - userwallet.proto: magic_multiplier + magic_max_credit
    - UserWalletService: پاپیولیت فیلدهای جدید + فالبک به SystemConstants
F7: ولیدیتورها از SystemConstants استفاده می‌کنند
    - WalletMaxSafeAmount (10B ریال) به عنوان حصار ایمنی
    - MagicWalletMinCharge، DiscountWalletMinCharge ثابت‌های مرکزی
    - سقف واقعی per-package در هندلرها اعمال می‌شود
Proto: v0.0.189
This commit is contained in:
masoodafar-web
2026-02-27 08:47:46 +03:30
parent e5bc3a952a
commit 61b7e4f8f2
18 changed files with 129 additions and 33 deletions
@@ -51,6 +51,15 @@ public class GetWithdrawalRequestsQueryHandler : IRequestHandler<GetWithdrawalRe
.PaginatedListAsync(paginationState: request.PaginationState) .PaginatedListAsync(paginationState: request.PaginationState)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
// دریافت PackageName از آخرین خرید پکیج کاربران
var userIds = models.Select(x => x.UserId).Distinct().ToList();
var userPackageNames = await _context.UserPackagePurchases
.Where(p => userIds.Contains(p.UserId))
.Include(p => p.Package)
.GroupBy(p => p.UserId)
.Select(g => new { UserId = g.Key, PackageName = g.OrderByDescending(x => x.PurchasedAt).First().Package.Title })
.ToDictionaryAsync(x => x.UserId, x => x.PackageName, cancellationToken);
var result = models.Select(x => new WithdrawalRequestModel var result = models.Select(x => new WithdrawalRequestModel
{ {
Id = x.Id, Id = x.Id,
@@ -69,7 +78,8 @@ public class GetWithdrawalRequestsQueryHandler : IRequestHandler<GetWithdrawalRe
BankReferenceId = x.BankReferenceId, BankReferenceId = x.BankReferenceId,
BankTrackingCode = x.BankTrackingCode, BankTrackingCode = x.BankTrackingCode,
PaymentFailureReason = x.PaymentFailureReason, PaymentFailureReason = x.PaymentFailureReason,
Created = x.Created Created = x.Created,
PackageName = userPackageNames.GetValueOrDefault(x.UserId)
}).ToList(); }).ToList();
return new GetWithdrawalRequestsResponseDto return new GetWithdrawalRequestsResponseDto
@@ -25,4 +25,5 @@ public class WithdrawalRequestModel
public string? BankTrackingCode { get; set; } public string? BankTrackingCode { get; set; }
public string? PaymentFailureReason { get; set; } public string? PaymentFailureReason { get; set; }
public DateTime Created { get; set; } public DateTime Created { get; set; }
public string? PackageName { get; set; }
} }
@@ -35,6 +35,7 @@ public interface IUserNotificationService
long userId, long userId,
decimal amount, decimal amount,
int weekNumber, int weekNumber,
string? packageName = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -42,6 +43,7 @@ public interface IUserNotificationService
/// </summary> /// </summary>
Task SendClubActivationNotificationAsync( Task SendClubActivationNotificationAsync(
long userId, long userId,
string? packageName = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary> /// <summary>
@@ -50,5 +52,6 @@ public interface IUserNotificationService
Task SendPayoutErrorNotificationAsync( Task SendPayoutErrorNotificationAsync(
long userId, long userId,
string errorMessage, string errorMessage,
string? packageName = null,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
} }
@@ -122,6 +122,7 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
contract.IsProcessed = true; contract.IsProcessed = true;
result.WasProcessed = true; result.WasProcessed = true;
result.NewWalletBalance = processResult.NewBalance; result.NewWalletBalance = processResult.NewBalance;
result.PackageName = processResult.PackageName;
result.Message = "وام پردازش و کیف پول شارژ شد"; result.Message = "وام پردازش و کیف پول شارژ شد";
processedCount++; processedCount++;
@@ -181,7 +182,7 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
/// <summary> /// <summary>
/// پردازش مالی وام دایا /// پردازش مالی وام دایا
/// </summary> /// </summary>
private async Task<(long NewBalance, long TransactionId)> ProcessDayaLoanAsync( private async Task<(long NewBalance, long TransactionId, string PackageName)> ProcessDayaLoanAsync(
User user, User user,
string contractNumber, string contractNumber,
CancellationToken cancellationToken) CancellationToken cancellationToken)
@@ -279,25 +280,25 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
await _context.SaveChangesAsync(cancellationToken); await _context.SaveChangesAsync(cancellationToken);
// 8. ارسال SMS // 8. ارسال SMS
await SendDayaLoanSmsAsync(user); await SendDayaLoanSmsAsync(user, package.Title);
return (wallet.Balance, transaction.Id); return (wallet.Balance, transaction.Id, package.Title);
} }
/// <summary> /// <summary>
/// ارسال SMS اطلاع‌رسانی /// ارسال SMS اطلاع‌رسانی
/// </summary> /// </summary>
private async Task SendDayaLoanSmsAsync(User user) private async Task SendDayaLoanSmsAsync(User user, string packageName)
{ {
try try
{ {
var userName = SmsTemplates.GetUserName(user.FirstName+" "+user.LastName); var userName = SmsTemplates.GetUserName(user.FirstName+" "+user.LastName);
var message = SmsTemplates.DayaLoanReceived(userName); var message = SmsTemplates.DayaLoanReceived(userName, packageName);
await _smsService.SendAsync(user.Mobile, message); await _smsService.SendAsync(user.Mobile, message);
// ارسال SMS به ادمین برای اطلاع // ارسال SMS به ادمین برای اطلاع
var adminMessage = $"وام دایا دریافت شد\nکاربر: {user.FirstName} {user.LastName}\nموبایل: {user.Mobile}\nکدملی: {user.NationalCode}"; var adminMessage = $"وام دایا دریافت شد\nکاربر: {user.FirstName} {user.LastName}\nموبایل: {user.Mobile}\nکدملی: {user.NationalCode}\nپکیج: {packageName}";
await _smsService.SendAsync("09199877503", adminMessage); await _smsService.SendAsync("09199877503", adminMessage);
_logger.LogInformation("Daya loan SMS sent to User {UserId}", user.Id); _logger.LogInformation("Daya loan SMS sent to User {UserId}", user.Id);
@@ -63,6 +63,11 @@ public class DayaLoanProcessResult
/// </summary> /// </summary>
public long? NewWalletBalance { get; set; } public long? NewWalletBalance { get; set; }
/// <summary>
/// نام پکیج
/// </summary>
public string? PackageName { get; set; }
/// <summary> /// <summary>
/// پیام /// پیام
/// </summary> /// </summary>
@@ -1,3 +1,4 @@
using CMSMicroservice.Domain.Common;
using FluentValidation; using FluentValidation;
namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.CreateManualPayment; namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.CreateManualPayment;
@@ -10,11 +11,12 @@ public class CreateManualPaymentCommandValidator : AbstractValidator<CreateManua
.GreaterThan(0) .GreaterThan(0)
.WithMessage("شناسه کاربر باید بزرگتر از صفر باشد"); .WithMessage("شناسه کاربر باید بزرگتر از صفر باشد");
// حصار ایمنی — مبلغ واقعی از پکیج کاربر خوانده می‌شود
RuleFor(x => x.Amount) RuleFor(x => x.Amount)
.GreaterThan(0) .GreaterThan(0)
.WithMessage("مبلغ باید بزرگتر از صفر باشد") .WithMessage("مبلغ باید بزرگتر از صفر باشد")
.LessThanOrEqualTo(1_000_000_000) .LessThanOrEqualTo(SystemConstants.WalletMaxSafeAmount)
.WithMessage("مبلغ نمی‌تواند بیشتر از 1 میلیارد ریال باشد"); .WithMessage("مبلغ وارد شده بیش از حد مجاز است");
RuleFor(x => x.Type) RuleFor(x => x.Type)
.IsInEnum() .IsInEnum()
@@ -69,6 +69,14 @@ public class GetAllManualPaymentsQueryHandler
// Pagination // Pagination
var skip = (request.PageNumber - 1) * request.PageSize; var skip = (request.PageNumber - 1) * request.PageSize;
// دریافت PackageName از آخرین خرید پکیج کاربر
var userPackageNames = await _context.UserPackagePurchases
.Include(p => p.Package)
.GroupBy(p => p.UserId)
.Select(g => new { UserId = g.Key, PackageName = g.OrderByDescending(x => x.PurchasedAt).First().Package.Title })
.ToDictionaryAsync(x => x.UserId, x => x.PackageName, cancellationToken);
var data = await query var data = await query
.Skip(skip) .Skip(skip)
.Take(request.PageSize) .Take(request.PageSize)
@@ -96,6 +104,13 @@ public class GetAllManualPaymentsQueryHandler
}) })
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
// تکمیل PackageName
foreach (var item in data)
{
if (userPackageNames.TryGetValue(item.UserId, out var pkgName))
item.PackageName = pkgName;
}
var metaData = new MetaData var metaData = new MetaData
{ {
TotalCount = totalCount, TotalCount = totalCount,
@@ -30,4 +30,5 @@ public class ManualPaymentDto
public string? RejectionReason { get; set; } public string? RejectionReason { get; set; }
public long? TransactionId { get; set; } public long? TransactionId { get; set; }
public DateTime Created { get; set; } public DateTime Created { get; set; }
public string? PackageName { get; set; }
} }
@@ -1,3 +1,4 @@
using CMSMicroservice.Domain.Common;
using FluentValidation; using FluentValidation;
namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeDiscountWallet; namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeDiscountWallet;
@@ -10,10 +11,11 @@ public class ChargeDiscountWalletCommandValidator : AbstractValidator<ChargeDisc
.GreaterThan(0) .GreaterThan(0)
.WithMessage("شناسه کاربر باید بزرگتر از صفر باشد"); .WithMessage("شناسه کاربر باید بزرگتر از صفر باشد");
// حصار ایمنی — سقف تخفیفی فعلاً سراسری است
RuleFor(x => x.Amount) RuleFor(x => x.Amount)
.GreaterThanOrEqualTo(10_000) .GreaterThanOrEqualTo(SystemConstants.DiscountWalletMinCharge)
.WithMessage("حداقل مبلغ شارژ 10,000 تومان است") .WithMessage("حداقل مبلغ شارژ ۱,۰۰۰ تومان است")
.LessThanOrEqualTo(1_000_000_000) .LessThanOrEqualTo(SystemConstants.WalletMaxSafeAmount)
.WithMessage("حداکثر مبلغ شارژ 1,000,000,000 تومان است"); .WithMessage("مبلغ وارد شده بیش از حد مجاز است");
} }
} }
@@ -1,3 +1,4 @@
using CMSMicroservice.Domain.Common;
using FluentValidation; using FluentValidation;
namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet; namespace CMSMicroservice.Application.WalletCQ.Commands.ChargeMagicWallet;
@@ -10,10 +11,11 @@ public class ChargeMagicWalletCommandValidator : AbstractValidator<ChargeMagicWa
.GreaterThan(0) .GreaterThan(0)
.WithMessage("شناسه کاربر باید بزرگتر از صفر باشد"); .WithMessage("شناسه کاربر باید بزرگتر از صفر باشد");
// حصار ایمنی — سقف واقعی per-package در هندلر اعمال می‌شود
RuleFor(x => x.Amount) RuleFor(x => x.Amount)
.GreaterThanOrEqualTo(100_000) .GreaterThanOrEqualTo(SystemConstants.MagicWalletMinCharge)
.WithMessage("حداقل مبلغ شارژ جادویی ۱۰,۰۰۰ تومان است") .WithMessage("حداقل مبلغ شارژ جادویی ۱۰,۰۰۰ تومان است")
.LessThanOrEqualTo(1_000_000_000) .LessThanOrEqualTo(SystemConstants.WalletMaxSafeAmount)
.WithMessage("حداکثر مبلغ شارژ جادویی ۱۰۰,۰۰۰,۰۰۰ تومان است"); .WithMessage("مبلغ وارد شده بیش از حد مجاز است");
} }
} }
@@ -8,14 +8,18 @@ public static class SmsTemplates
/// <summary> /// <summary>
/// پیام دریافت اعتبار دایا /// پیام دریافت اعتبار دایا
/// </summary> /// </summary>
public static string DayaLoanReceived(string userName) => public static string DayaLoanReceived(string userName, string? packageName = null) =>
$"{userName} عزیز، تامین اعتبار شما انجام شد. شما می‌توانید با مراجعه به سامانه کارابازار فرایند فعال‌سازی خود را ادامه دهید. با تشکر از حسن انتخاب شما"; packageName != null
? $"{userName} عزیز، تامین اعتبار {packageName} شما انجام شد. شما می‌توانید با مراجعه به سامانه کارابازار فرایند فعال‌سازی خود را ادامه دهید. با تشکر از حسن انتخاب شما"
: $"{userName} عزیز، تامین اعتبار شما انجام شد. شما می‌توانید با مراجعه به سامانه کارابازار فرایند فعال‌سازی خود را ادامه دهید. با تشکر از حسن انتخاب شما";
/// <summary> /// <summary>
/// پیام فعال‌سازی باشگاه مشتریان /// پیام فعال‌سازی باشگاه مشتریان
/// </summary> /// </summary>
public static string ClubActivated(string userName) => public static string ClubActivated(string userName, string? packageName = null) =>
$"{userName} گرامی، عضویت شما در باشگاه مشتریان کارابازار با موفقیت فعال شد. با تشکر از حسن انتخاب شما"; packageName != null
? $"{userName} گرامی، عضویت شما در باشگاه مشتریان کارابازار با {packageName} فعال شد. با تشکر از حسن انتخاب شما"
: $"{userName} گرامی، عضویت شما در باشگاه مشتریان کارابازار با موفقیت فعال شد. با تشکر از حسن انتخاب شما";
/// <summary> /// <summary>
/// پیام خرید پکیج /// پیام خرید پکیج
@@ -26,8 +30,10 @@ public static class SmsTemplates
/// <summary> /// <summary>
/// پیام واریز کمیسیون /// پیام واریز کمیسیون
/// </summary> /// </summary>
public static string CommissionDeposited(string userName, long amount) => public static string CommissionDeposited(string userName, long amount, string? packageName = null) =>
$"{userName} گرامی، مبلغ {amount:N0} ریال بابت کمیسیون به کیف پول شما واریز شد."; packageName != null
? $"{userName} گرامی، مبلغ {amount:N0} ریال بابت کمیسیون {packageName} به کیف پول شما واریز شد."
: $"{userName} گرامی، مبلغ {amount:N0} ریال بابت کمیسیون به کیف پول شما واریز شد.";
/// <summary> /// <summary>
/// پیام برداشت موفق /// پیام برداشت موفق
@@ -66,6 +66,41 @@ public static class SystemConstants
/// </summary> /// </summary>
public const long MagicWalletEntryThreshold = 1_000_000; public const long MagicWalletEntryThreshold = 1_000_000;
/// <summary>
/// مقدار پیش‌فرض ضریب جادویی وقتی پکیج یافت نشود (بازگشت به پایه)
/// </summary>
public const double MagicWalletDefaultMultiplier = 2.5;
/// <summary>
/// مقدار پیش‌فرض حداکثر واریز جادویی وقتی پکیج یافت نشود (ریال)
/// </summary>
public const long MagicWalletDefaultMaxDeposit = 1_000_000_000;
/// <summary>
/// مقدار پیش‌فرض حداکثر اعتبار جادویی وقتی پکیج یافت نشود (ریال)
/// </summary>
public const long MagicWalletDefaultMaxCredit = 2_500_000_000;
#endregion
#region Wallet Validation Settings
/// <summary>
/// حداکثر مبلغ مجاز در ولیدیتور (حصار ایمنی — مقدار واقعی از پکیج خوانده می‌شود).
/// ۱۰ میلیارد ریال = ۱ میلیارد تومان
/// </summary>
public const long WalletMaxSafeAmount = 10_000_000_000;
/// <summary>
/// حداقل مبلغ شارژ جادویی (ریال) — ۱۰۰٬۰۰۰ ریال = ۱۰ هزار تومان
/// </summary>
public const long MagicWalletMinCharge = 100_000;
/// <summary>
/// حداقل مبلغ شارژ تخفیفی (ریال) — ۱۰٬۰۰۰ ریال = ۱ هزار تومان
/// </summary>
public const long DiscountWalletMinCharge = 10_000;
#endregion #endregion
#region Shop Settings #region Shop Settings
@@ -49,6 +49,7 @@ public class UserNotificationService : IUserNotificationService
long userId, long userId,
decimal amount, decimal amount,
int weekNumber, int weekNumber,
string? packageName = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
_logger.LogInformation( _logger.LogInformation(
@@ -69,14 +70,15 @@ public class UserNotificationService : IUserNotificationService
if (string.IsNullOrEmpty(userFullName)) userFullName = "کاربر عزیز"; if (string.IsNullOrEmpty(userFullName)) userFullName = "کاربر عزیز";
var formattedAmount = amount.ToString("N0", new System.Globalization.CultureInfo("fa-IR")); var formattedAmount = amount.ToString("N0", new System.Globalization.CultureInfo("fa-IR"));
var packageInfo = !string.IsNullOrEmpty(packageName) ? $" ({packageName})" : "";
// Send Email // Send Email
if (_emailSettings.Enabled && !string.IsNullOrEmpty(user.Email)) if (_emailSettings.Enabled && !string.IsNullOrEmpty(user.Email))
{ {
var emailSubject = $"واریز کمیسیون هفته {weekNumber}"; var emailSubject = $"واریز کمیسیون هفته {weekNumber}{packageInfo}";
var emailBody = "<div dir='rtl' style='font-family: Tahoma, Arial; text-align: right;'>" + var emailBody = "<div dir='rtl' style='font-family: Tahoma, Arial; text-align: right;'>" +
$"<h2>سلام {userFullName}</h2>" + $"<h2>سلام {userFullName}</h2>" +
$"<p>کمیسیون هفته {weekNumber} شما به مبلغ <strong>{formattedAmount} ریال</strong> به کیف پول شما واریز شد.</p>" + $"<p>کمیسیون هفته {weekNumber} شما{packageInfo} به مبلغ <strong>{formattedAmount} ریال</strong> به کیف پول شما واریز شد.</p>" +
"<p>از اعتماد شما سپاسگزاریم.</p>" + "<p>از اعتماد شما سپاسگزاریم.</p>" +
"<hr/>" + "<hr/>" +
"<p style='color: #666; font-size: 12px;'>FourSat - سیستم مدیریت باشگاه مشتریان</p>" + "<p style='color: #666; font-size: 12px;'>FourSat - سیستم مدیریت باشگاه مشتریان</p>" +
@@ -95,7 +97,7 @@ public class UserNotificationService : IUserNotificationService
{ {
await SendSmsAsync( await SendSmsAsync(
phoneNumber: user.Mobile, phoneNumber: user.Mobile,
message: $"سلام {userFullName}\nکمیسیون هفته {weekNumber} شما به مبلغ {formattedAmount} ریال واریز شد.\nFourSat", message: $"سلام {userFullName}\nکمیسیون هفته {weekNumber} شما{packageInfo} به مبلغ {formattedAmount} ریال واریز شد.\nFourSat",
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
} }
@@ -109,6 +111,7 @@ public class UserNotificationService : IUserNotificationService
public async Task SendClubActivationNotificationAsync( public async Task SendClubActivationNotificationAsync(
long userId, long userId,
string? packageName = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
_logger.LogInformation("🎉 Sending club activation notification: User={UserId}", userId); _logger.LogInformation("🎉 Sending club activation notification: User={UserId}", userId);
@@ -120,6 +123,7 @@ public class UserNotificationService : IUserNotificationService
var userFullName = $"{user.FirstName} {user.LastName}".Trim(); var userFullName = $"{user.FirstName} {user.LastName}".Trim();
if (string.IsNullOrEmpty(userFullName)) userFullName = "کاربر عزیز"; if (string.IsNullOrEmpty(userFullName)) userFullName = "کاربر عزیز";
var packageInfo = !string.IsNullOrEmpty(packageName) ? $" با {packageName}" : "";
// Send Email // Send Email
if (_emailSettings.Enabled && !string.IsNullOrEmpty(user.Email)) if (_emailSettings.Enabled && !string.IsNullOrEmpty(user.Email))
@@ -127,7 +131,7 @@ public class UserNotificationService : IUserNotificationService
var emailSubject = "فعال‌سازی باشگاه مشتریان FourSat"; var emailSubject = "فعال‌سازی باشگاه مشتریان FourSat";
var emailBody = "<div dir='rtl' style='font-family: Tahoma, Arial; text-align: right;'>" + var emailBody = "<div dir='rtl' style='font-family: Tahoma, Arial; text-align: right;'>" +
$"<h2>تبریک {userFullName}!</h2>" + $"<h2>تبریک {userFullName}!</h2>" +
"<p>عضویت شما در <strong>باشگاه مشتریان FourSat</strong> با موفقیت فعال شد.</p>" + $"<p>عضویت شما در <strong>باشگاه مشتریان FourSat</strong>{packageInfo} با موفقیت فعال شد.</p>" +
"<p>از این پس می‌توانید از مزایای ویژه باشگاه بهره‌مند شوید.</p>" + "<p>از این پس می‌توانید از مزایای ویژه باشگاه بهره‌مند شوید.</p>" +
"<hr/>" + "<hr/>" +
"<p style='color: #666; font-size: 12px;'>FourSat - سیستم مدیریت باشگاه مشتریان</p>" + "<p style='color: #666; font-size: 12px;'>FourSat - سیستم مدیریت باشگاه مشتریان</p>" +
@@ -146,7 +150,7 @@ public class UserNotificationService : IUserNotificationService
{ {
await SendSmsAsync( await SendSmsAsync(
phoneNumber: user.Mobile, phoneNumber: user.Mobile,
message: $"تبریک! عضویت شما در باشگاه مشتریان FourSat فعال شد.", message: $"تبریک! عضویت شما در باشگاه مشتریان FourSat{packageInfo} فعال شد.",
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
} }
} }
@@ -159,6 +163,7 @@ public class UserNotificationService : IUserNotificationService
public async Task SendPayoutErrorNotificationAsync( public async Task SendPayoutErrorNotificationAsync(
long userId, long userId,
string errorMessage, string errorMessage,
string? packageName = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
_logger.LogWarning( _logger.LogWarning(
@@ -3,7 +3,7 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Version>0.0.188</Version> <Version>0.0.189</Version>
<DebugType>None</DebugType> <DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols> <DebugSymbols>False</DebugSymbols>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild> <GeneratePackageOnBuild>False</GeneratePackageOnBuild>
@@ -531,6 +531,7 @@ message WithdrawalRequestModel
string bank_reference_id = 15; string bank_reference_id = 15;
string bank_tracking_code = 16; string bank_tracking_code = 16;
string payment_failure_reason = 17; string payment_failure_reason = 17;
google.protobuf.StringValue package_name = 18; // نام پکیج کاربر
} }
// ============ Week Definitions (for dropdowns) ============ // ============ Week Definitions (for dropdowns) ============
@@ -138,6 +138,7 @@ message ManualPaymentModel
google.protobuf.Timestamp created = 19; google.protobuf.Timestamp created = 19;
google.protobuf.StringValue image_path = 20; google.protobuf.StringValue image_path = 20;
google.protobuf.Int64Value image_document_id = 21; google.protobuf.Int64Value image_document_id = 21;
google.protobuf.StringValue package_name = 22; // نام پکیج کاربر
} }
message ProcessManualMembershipPaymentRequest message ProcessManualMembershipPaymentRequest
@@ -251,6 +251,8 @@ message GetMagicWalletStatusResponse
int64 balance = 6; int64 balance = 6;
google.protobuf.Timestamp magic_activated_at = 7; google.protobuf.Timestamp magic_activated_at = 7;
int32 purchase_cycle_count = 8; // تعداد دورهای خرید پکیج (0 = هنوز هیچ دوری تکمیل نشده) int32 purchase_cycle_count = 8; // تعداد دورهای خرید پکیج (0 = هنوز هیچ دوری تکمیل نشده)
double magic_multiplier = 9; // ضریب جادویی (مثلاً 2.5)
int64 magic_max_credit = 10; // حداکثر اعتبار (ریال)
} }
// ============= Discount Wallet Messages ============= // ============= Discount Wallet Messages =============
@@ -233,7 +233,9 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
var package = currentCycle != null var package = currentCycle != null
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, context.CancellationToken) ? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, context.CancellationToken)
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken); : await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken);
var magicMaxDeposit = package?.MagicWalletMaxDeposit ?? 1_000_000_000; var magicMaxDeposit = package?.MagicWalletMaxDeposit ?? SystemConstants.MagicWalletDefaultMaxDeposit;
var magicMultiplier = package != null ? (double)package.MagicWalletMultiplier : SystemConstants.MagicWalletDefaultMultiplier;
var magicMaxCredit = package?.MagicWalletMaxCredit ?? SystemConstants.MagicWalletDefaultMaxCredit;
var response = new GetMagicWalletStatusResponse var response = new GetMagicWalletStatusResponse
{ {
@@ -243,7 +245,9 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
MagicMaxDeposit = magicMaxDeposit, MagicMaxDeposit = magicMaxDeposit,
MagicRemainingDeposit = Math.Max(0, magicMaxDeposit - wallet.MagicTotalDeposited), MagicRemainingDeposit = Math.Max(0, magicMaxDeposit - wallet.MagicTotalDeposited),
Balance = wallet.Balance, Balance = wallet.Balance,
PurchaseCycleCount = cycleCount PurchaseCycleCount = cycleCount,
MagicMultiplier = magicMultiplier,
MagicMaxCredit = magicMaxCredit
}; };
if (wallet.MagicActivatedAt.HasValue) if (wallet.MagicActivatedAt.HasValue)