- Added FINAL-STATUS.md detailing project completion and key metrics - Created QUICK-REFERENCE.md for quick access to essential documents - Updated README.md with project overview and quick start guide - Established STRUCTURE.md outlining the final documentation structure - Organized and archived old files, ensuring a clean and efficient directory - Enhanced documentation quality with comprehensive metrics and checklists
35 KiB
Package Purchase System - سیستم خرید پکیج طلایی
تاریخ ایجاد: 2024-12-02
وضعیت: در حال طراحی
اولویت: 🔴 بسیار بالا
📋 فهرست
- مقدمه
- سه سناریوی اصلی
- Entity Changes
- Business Rules
- Flow Diagrams
- Commands & Handlers
- تسکهای پیادهسازی
🎯 مقدمه
سیستم خرید پکیج طلایی سه سناریوی مختلف دارد که باید به درستی از هم تفکیک شوند:
هدف کلی:
- سناریو 1 و 2: خرید پکیج طلایی (56 میلیون تومان) → امکان فعالسازی باشگاه مشتریان
- سناریو 3: شارژ عادی کیف پول تخفیفی → فقط برای خرید از فروشگاه تخفیفی
نکات کلیدی:
- کاربر فقط یک بار میتواند پکیج طلایی خریداری کند (سناریو 1 یا 2)
- بعد از خرید پکیج، کاربر باید خودش دکمه فعالسازی باشگاه را بزند
- فعالسازی باشگاه نیاز به تایید Admin ندارد
- عضویت در شبکه (NetworkMembership) جدا از عضویت در باشگاه (ClubMembership) است
- کمیسیونها فقط بعد از فعالسازی باشگاه محاسبه میشوند
🔄 سه سناریوی اصلی
📌 سناریو 1: دریافت وام دایا (DayaLoan)
کاربر → درخواست وام از دایا → دایا وام را تایید میکند
↓
شارژ Balance در UserWallet (56,000,000 تومان)
↓
ثبت UserWalletChangeLog (Amount: +56M, Type: DepositExternal1)
↓
ثبت Transaction (Type: DepositExternal1, RefId: شماره قرارداد دایا)
↓
ثبت UserOrder (PackageId: پکیج طلایی, TransactionId: xxx, Amount: 56M)
↓
کاربر میتواند با این 56M از فروشگاه عادی خرید کند
↓
[کاربر باید خودش دکمه "فعالسازی باشگاه مشتریان" را بزند]
↓
ثبت/بهروزرسانی ClubMembership (IsActive: true, PurchaseMethod: DayaLoan)
↓
شروع محاسبه کمیسیونها
نکات:
Transaction.Type=DepositExternal1(وام دایا)Transaction.RefId= شماره قرارداد دایاUserOrder.PackageIdپر میشودUser.PackagePurchaseMethod=DayaLoan
📌 سناریو 2: خرید پکیج طلایی از درگاه (Direct Purchase)
کاربر → انتخاب پکیج طلایی (56M) → کلیک "پرداخت"
↓
ثبت UserOrder (PackageId: پکیج طلایی, Amount: 56M, PaymentStatus: Pending)
↓
Redirect به درگاه بانکی (IPG)
↓
کاربر پرداخت میکند و بر میگردد
↓
Verify پرداخت با بانک
↓
شارژ Balance در UserWallet (56,000,000 تومان)
↓
ثبت UserWalletChangeLog (Amount: +56M, Type: DepositIpg)
↓
ثبت Transaction (Type: DepositIpg, RefId: کد پیگیری بانک)
↓
بهروزرسانی UserOrder (TransactionId: xxx, PaymentStatus: Success)
↓
کاربر میتواند با این 56M از فروشگاه عادی خرید کند
↓
[کاربر باید خودش دکمه "فعالسازی باشگاه مشتریان" را بزند]
↓
ثبت/بهروزرسانی ClubMembership (IsActive: true, PurchaseMethod: DirectPurchase)
↓
شروع محاسبه کمیسیونها
نکات:
Transaction.Type=DepositIpg(پرداخت از درگاه)Transaction.RefId= کد پیگیری بانکUserOrder.PackageIdپر میشودUser.PackagePurchaseMethod=DirectPurchase
📌 سناریو 3: شارژ عادی کیف پول تخفیفی (Regular Wallet Charge)
کاربر → انتخاب مبلغ دلخواه → کلیک "شارژ کیف پول"
↓
Redirect به درگاه بانکی (IPG)
↓
کاربر پرداخت میکند و بر میگردد
↓
Verify پرداخت با بانک
↓
شارژ DiscountBalance در UserWallet (مبلغ دلخواه)
↓
ثبت UserWalletChangeLog (Amount: +xxx, Type: DiscountWalletCharge)
↓
ثبت Transaction (Type: DiscountWalletCharge, RefId: کد پیگیری بانک)
↓
کاربر میتواند فقط از فروشگاه تخفیفی خرید کند
↓
[هیچ ارتباطی با باشگاه مشتریان ندارد]
نکات:
Transaction.Type=DiscountWalletChargeTransaction.RefId= کد پیگیری بانک- PackageId در هیچ جا ثبت نمیشود
- فقط
DiscountBalanceشارژ میشود، نهBalance - هیچ
UserOrderباPackageIdثبت نمیشود
🗄️ Entity Changes
1️⃣ Enum جدید: PackagePurchaseMethod
namespace CMSMicroservice.Domain.Enums;
/// <summary>
/// نحوه خرید پکیج طلایی توسط کاربر
/// </summary>
public enum PackagePurchaseMethod
{
/// <summary>
/// هنوز پکیج خریداری نکرده
/// </summary>
None = 0,
/// <summary>
/// از طریق وام دایا
/// </summary>
DayaLoan = 1,
/// <summary>
/// از طریق پرداخت مستقیم درگاه بانکی
/// </summary>
DirectPurchase = 2
}
محل: CMS/src/CMSMicroservice.Domain/Enums/PackagePurchaseMethod.cs
2️⃣ تغییرات User Entity
// اضافه کردن این فیلد به User.cs:
/// <summary>
/// نحوه خرید پکیج طلایی (برای جلوگیری از خرید مجدد)
/// </summary>
public PackagePurchaseMethod PackagePurchaseMethod { get; set; } = PackagePurchaseMethod.None;
منطق:
- وقتی کاربر سناریو 1 یا 2 را انجام میدهد، این فیلد تغییر میکند
- اگر
PackagePurchaseMethod != Noneباشد، کاربر نمیتواند دوباره پکیج خریداری کند
3️⃣ تغییرات ClubMembership Entity
// اضافه کردن این فیلد به ClubMembership.cs:
/// <summary>
/// نحوه خرید پکیج که منجر به فعالسازی باشگاه شد
/// </summary>
public PackagePurchaseMethod PurchaseMethod { get; set; }
منطق:
- وقتی کاربر دکمه "فعالسازی باشگاه" را میزند، این فیلد از
User.PackagePurchaseMethodکپی میشود - برای گزارشگیری و تحلیل: چند نفر از طریق وام دایا و چند نفر از طریق خرید مستقیم عضو شدند
4️⃣ تغییرات TransactionType Enum
// فعلاً موجود است:
public enum TransactionType
{
Buy = 0,
DepositIpg = 1, // پرداخت از درگاه (سناریو 2)
DepositExternal1 = 2, // وام دایا (سناریو 1)
Withdraw = 3,
NetworkCommission = 10,
ClubActivation = 11,
DiscountWalletCharge = 12 // شارژ کیف پول تخفیفی (سناریو 3) ✅
}
نکته: DiscountWalletCharge از قبل وجود دارد، پس نیازی به تغییر نیست.
📐 Business Rules
قانون 1: یک کاربر فقط یک بار میتواند پکیج طلایی خریداری کند
// Check قبل از خرید پکیج:
if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
{
throw new ValidationException("شما قبلاً پکیج طلایی را خریداری کردهاید.");
}
قانون 2: فعالسازی باشگاه فقط با موجودی اصلی (Balance) امکانپذیر است
// Check موقع فعالسازی باشگاه:
var userWallet = await _context.UserWallets
.FirstOrDefaultAsync(w => w.UserId == userId);
if (userWallet.Balance < 56_000_000)
{
throw new ValidationException("برای فعالسازی باشگاه مشتریان باید حداقل 56 میلیون تومان موجودی اصلی داشته باشید.");
}
قانون 3: فعالسازی باشگاه فقط برای کسانی که پکیج خریدهاند
// Check موقع فعالسازی باشگاه:
if (user.PackagePurchaseMethod == PackagePurchaseMethod.None)
{
throw new ValidationException("برای فعالسازی باشگاه مشتریان ابتدا باید پکیج طلایی خریداری کنید.");
}
// پیدا کردن UserOrder مربوط به پکیج:
var packageOrder = await _context.UserOrders
.FirstOrDefaultAsync(o =>
o.UserId == userId &&
o.PackageId != null &&
o.PaymentStatus == PaymentStatus.Success
);
if (packageOrder == null)
{
throw new ValidationException("سفارش پکیج طلایی یافت نشد.");
}
// پیدا کردن Transaction مربوطه:
var transaction = await _context.Transactions
.FirstOrDefaultAsync(t => t.Id == packageOrder.TransactionId);
if (transaction == null ||
(transaction.Type != TransactionType.DepositIpg &&
transaction.Type != TransactionType.DepositExternal1))
{
throw new ValidationException("تراکنش معتبر برای فعالسازی باشگاه یافت نشد.");
}
قانون 4: NetworkMembership جدا از ClubMembership است
- NetworkMembership: موقع ثبتنام کاربر خودکار ایجاد میشود (با
ParentId) - ClubMembership: فقط وقتی کاربر دکمه "فعالسازی باشگاه" را بزند ایجاد میشود
- کاربر میتواند زیرمجموعه بگیرد بدون اینکه جزو باشگاه باشد (ولی سیاستگذاری میکنیم که قبل از گرفتن زیرمجموعه باید باشگاه را فعال کرده باشد)
قانون 5: محاسبه کمیسیون فقط بعد از فعالسازی باشگاه
// در محاسبه کمیسیون:
var clubMembership = await _context.ClubMemberships
.FirstOrDefaultAsync(c => c.UserId == userId && c.IsActive);
if (clubMembership == null)
{
// این کاربر کمیسیون نمیگیرد چون جزو باشگاه نیست
return;
}
// ادامه محاسبه کمیسیون...
📊 Flow Diagrams
🔹 Flow 1: خرید پکیج از درگاه (سناریو 2)
┌─────────────────────────────────────────────────────────────┐
│ FrontOffice UI (کاربر) │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────┐
│ انتخاب پکیج طلایی (56M) │
└────────────┬─────────────┘
│
▼
┌──────────────────────────────────────┐
│ PurchaseGoldenPackageCommand │
│ - بررسی User.PackagePurchaseMethod │
│ - ثبت UserOrder (Pending) │
│ - Redirect به درگاه │
└────────────┬─────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ درگاه بانکی (IPG) │
│ کاربر پرداخت میکند │
└────────────┬─────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ VerifyGoldenPackagePurchaseCommand │
│ - Verify با بانک │
│ - شارژ UserWallet.Balance (56M) │
│ - ثبت Transaction (DepositIpg) │
│ - ثبت UserWalletChangeLog │
│ - Set User.PackagePurchaseMethod │
│ = DirectPurchase │
│ - بهروزرسانی UserOrder (Success) │
└────────────┬─────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ کاربر میتواند از فروشگاه عادی │
│ خرید کند (با Balance) │
└──────────────────────────────────────┘
🔹 Flow 2: فعالسازی باشگاه مشتریان
┌─────────────────────────────────────────────────────────────┐
│ FrontOffice UI (کاربر وارد شده) │
│ کاربر دکمه "فعالسازی باشگاه" را میزند │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ ActivateClubMembershipCommand │
│ │
│ 1. بررسی User.PackagePurchaseMethod │
│ → باید != None باشد │
│ │
│ 2. بررسی UserWallet.Balance │
│ → باید >= 56M باشد │
│ │
│ 3. پیدا کردن UserOrder با PackageId │
│ → PaymentStatus = Success │
│ │
│ 4. پیدا کردن Transaction │
│ → Type = DepositIpg یا │
│ DepositExternal1 │
│ │
│ 5. ثبت/بهروزرسانی ClubMembership │
│ - IsActive = true │
│ - ActivatedAt = DateTime.Now │
│ - PurchaseMethod = کپی از User │
│ │
└────────────┬─────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ کاربر جزو باشگاه مشتریان شد │
│ کمیسیونها شروع به محاسبه میکنند │
└──────────────────────────────────────┘
🔹 Flow 3: شارژ کیف پول تخفیفی (سناریو 3)
┌─────────────────────────────────────────────────────────────┐
│ FrontOffice UI (کاربر) │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────┐
│ انتخاب مبلغ دلخواه │
│ (برای فروشگاه تخفیفی) │
└────────────┬─────────────┘
│
▼
┌──────────────────────────────────────┐
│ ChargeDiscountWalletCommand │
│ - Redirect به درگاه │
└────────────┬─────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ درگاه بانکی (IPG) │
│ کاربر پرداخت میکند │
└────────────┬─────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ VerifyDiscountWalletChargeCommand │
│ - Verify با بانک │
│ - شارژ UserWallet.DiscountBalance │
│ - ثبت Transaction │
│ (Type: DiscountWalletCharge) │
│ - ثبت UserWalletChangeLog │
└────────────┬─────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ کاربر میتواند از فروشگاه تخفیفی │
│ خرید کند (با DiscountBalance) │
└──────────────────────────────────────┘
نکته: در این سناریو هیچ UserOrder با PackageId ثبت نمیشود.
💻 Commands & Handlers
1️⃣ PurchaseGoldenPackageCommand
مسئولیت: ایجاد سفارش پکیج طلایی و Redirect به درگاه
public class PurchaseGoldenPackageCommand : IRequest<PaymentInitiateResult>
{
public long UserId { get; set; }
}
public class PurchaseGoldenPackageCommandHandler
: IRequestHandler<PurchaseGoldenPackageCommand, PaymentInitiateResult>
{
private readonly IApplicationDbContext _context;
private readonly IPaymentGatewayService _paymentGateway;
public async Task<PaymentInitiateResult> Handle(
PurchaseGoldenPackageCommand request,
CancellationToken cancellationToken)
{
// 1. بررسی User
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
if (user == null)
throw new NotFoundException(nameof(User), request.UserId);
// 2. بررسی اینکه قبلاً پکیج نخریده باشد
if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
{
throw new ValidationException("شما قبلاً پکیج طلایی را خریداری کردهاید.");
}
// 3. پیدا کردن پکیج طلایی
var goldenPackage = await _context.Packages
.FirstOrDefaultAsync(p => p.Title.Contains("طلایی"), cancellationToken);
if (goldenPackage == null)
throw new NotFoundException("پکیج طلایی یافت نشد.");
// 4. ایجاد UserOrder
var order = new UserOrder
{
UserId = user.Id,
PackageId = goldenPackage.Id,
Amount = goldenPackage.Price, // 56,000,000
PaymentStatus = PaymentStatus.Pending,
DeliveryStatus = DeliveryStatus.None,
UserAddressId = 0 // پکیج نیاز به آدرس ندارد
};
_context.UserOrders.Add(order);
await _context.SaveChangesAsync(cancellationToken);
// 5. Redirect به درگاه
var paymentRequest = new PaymentRequest
{
Amount = order.Amount,
OrderId = order.Id.ToString(),
CallbackUrl = "https://yourdomain.com/verify-golden-package",
Description = $"خرید پکیج طلایی"
};
var result = await _paymentGateway.InitiatePaymentAsync(paymentRequest);
return result;
}
}
2️⃣ VerifyGoldenPackagePurchaseCommand
مسئولیت: Verify پرداخت و شارژ کیف پول
public class VerifyGoldenPackagePurchaseCommand : IRequest<bool>
{
public long OrderId { get; set; }
public string Authority { get; set; } // از درگاه
}
public class VerifyGoldenPackagePurchaseCommandHandler
: IRequestHandler<VerifyGoldenPackagePurchaseCommand, bool>
{
private readonly IApplicationDbContext _context;
private readonly IPaymentGatewayService _paymentGateway;
public async Task<bool> Handle(
VerifyGoldenPackagePurchaseCommand request,
CancellationToken cancellationToken)
{
// 1. پیدا کردن Order
var order = await _context.UserOrders
.Include(o => o.Package)
.Include(o => o.User)
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
if (order == null)
throw new NotFoundException(nameof(UserOrder), request.OrderId);
// 2. Verify با بانک
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
request.Authority,
order.Amount
);
if (!verifyResult.IsSuccess)
{
order.PaymentStatus = PaymentStatus.Failed;
await _context.SaveChangesAsync(cancellationToken);
return false;
}
// 3. شارژ کیف پول
var wallet = await _context.UserWallets
.FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken);
wallet.Balance += order.Amount; // 56,000,000
// 4. ثبت Transaction
var transaction = new Transactions
{
Amount = order.Amount,
Description = "خرید پکیج طلایی از درگاه",
PaymentStatus = PaymentStatus.Success,
PaymentDate = DateTime.Now,
RefId = verifyResult.RefId,
Type = TransactionType.DepositIpg
};
_context.Transactions.Add(transaction);
await _context.SaveChangesAsync(cancellationToken);
// 5. ثبت ChangeLog
var changeLog = new UserWalletChangeLog
{
UserId = order.UserId,
Amount = order.Amount,
ChangeType = WalletChangeType.Deposit,
Description = "شارژ موجودی از پکیج طلایی",
BalanceBefore = wallet.Balance - order.Amount,
BalanceAfter = wallet.Balance
};
_context.UserWalletChangeLogs.Add(changeLog);
// 6. بهروزرسانی Order
order.TransactionId = transaction.Id;
order.PaymentStatus = PaymentStatus.Success;
order.PaymentDate = DateTime.Now;
order.PaymentMethod = PaymentMethod.Online;
// 7. تغییر User.PackagePurchaseMethod
order.User.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase;
await _context.SaveChangesAsync(cancellationToken);
return true;
}
}
3️⃣ ActivateClubMembershipCommand
مسئولیت: فعالسازی عضویت در باشگاه مشتریان
public class ActivateClubMembershipCommand : IRequest<bool>
{
public long UserId { get; set; }
}
public class ActivateClubMembershipCommandHandler
: IRequestHandler<ActivateClubMembershipCommand, bool>
{
private readonly IApplicationDbContext _context;
public async Task<bool> Handle(
ActivateClubMembershipCommand request,
CancellationToken cancellationToken)
{
// 1. بررسی User
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
if (user == null)
throw new NotFoundException(nameof(User), request.UserId);
// 2. بررسی اینکه پکیج خریده باشد
if (user.PackagePurchaseMethod == PackagePurchaseMethod.None)
{
throw new ValidationException(
"برای فعالسازی باشگاه مشتریان ابتدا باید پکیج طلایی خریداری کنید."
);
}
// 3. بررسی موجودی
var wallet = await _context.UserWallets
.FirstOrDefaultAsync(w => w.UserId == user.Id, cancellationToken);
if (wallet.Balance < 56_000_000)
{
throw new ValidationException(
"برای فعالسازی باشگاه مشتریان باید حداقل 56 میلیون تومان موجودی اصلی داشته باشید."
);
}
// 4. بررسی UserOrder
var packageOrder = await _context.UserOrders
.FirstOrDefaultAsync(o =>
o.UserId == user.Id &&
o.PackageId != null &&
o.PaymentStatus == PaymentStatus.Success,
cancellationToken
);
if (packageOrder == null)
{
throw new ValidationException("سفارش پکیج طلایی یافت نشد.");
}
// 5. بررسی Transaction
var transaction = await _context.Transactions
.FirstOrDefaultAsync(t => t.Id == packageOrder.TransactionId, cancellationToken);
if (transaction == null ||
(transaction.Type != TransactionType.DepositIpg &&
transaction.Type != TransactionType.DepositExternal1))
{
throw new ValidationException("تراکنش معتبر برای فعالسازی باشگاه یافت نشد.");
}
// 6. بررسی اینکه قبلاً فعال نکرده باشد
var existingMembership = await _context.ClubMemberships
.FirstOrDefaultAsync(c => c.UserId == user.Id, cancellationToken);
if (existingMembership != null && existingMembership.IsActive)
{
throw new ValidationException("شما قبلاً عضو باشگاه مشتریان هستید.");
}
// 7. ثبت یا بهروزرسانی ClubMembership
if (existingMembership == null)
{
existingMembership = new ClubMembership
{
UserId = user.Id,
IsActive = true,
ActivatedAt = DateTime.Now,
InitialContribution = 56_000_000,
TotalEarned = 0,
PurchaseMethod = user.PackagePurchaseMethod
};
_context.ClubMemberships.Add(existingMembership);
}
else
{
existingMembership.IsActive = true;
existingMembership.ActivatedAt = DateTime.Now;
existingMembership.PurchaseMethod = user.PackagePurchaseMethod;
}
await _context.SaveChangesAsync(cancellationToken);
return true;
}
}
4️⃣ ChargeDiscountWalletCommand (سناریو 3)
مسئولیت: شارژ کیف پول تخفیفی
public class ChargeDiscountWalletCommand : IRequest<PaymentInitiateResult>
{
public long UserId { get; set; }
public long Amount { get; set; }
}
public class ChargeDiscountWalletCommandHandler
: IRequestHandler<ChargeDiscountWalletCommand, PaymentInitiateResult>
{
private readonly IApplicationDbContext _context;
private readonly IPaymentGatewayService _paymentGateway;
public async Task<PaymentInitiateResult> Handle(
ChargeDiscountWalletCommand request,
CancellationToken cancellationToken)
{
// 1. بررسی User
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
if (user == null)
throw new NotFoundException(nameof(User), request.UserId);
// 2. بررسی مبلغ (حداقل 10,000 تومان)
if (request.Amount < 10_000)
{
throw new ValidationException("حداقل مبلغ شارژ 10,000 تومان است.");
}
// 3. Redirect به درگاه
var paymentRequest = new PaymentRequest
{
Amount = request.Amount,
OrderId = $"DISCOUNT_{user.Id}_{DateTime.Now:yyyyMMddHHmmss}",
CallbackUrl = "https://yourdomain.com/verify-discount-wallet",
Description = $"شارژ کیف پول تخفیفی"
};
var result = await _paymentGateway.InitiatePaymentAsync(paymentRequest);
return result;
}
}
5️⃣ VerifyDiscountWalletChargeCommand (سناریو 3)
مسئولیت: Verify و شارژ DiscountBalance
public class VerifyDiscountWalletChargeCommand : IRequest<bool>
{
public long UserId { get; set; }
public long Amount { get; set; }
public string Authority { get; set; }
}
public class VerifyDiscountWalletChargeCommandHandler
: IRequestHandler<VerifyDiscountWalletChargeCommand, bool>
{
private readonly IApplicationDbContext _context;
private readonly IPaymentGatewayService _paymentGateway;
public async Task<bool> Handle(
VerifyDiscountWalletChargeCommand request,
CancellationToken cancellationToken)
{
// 1. پیدا کردن User
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
if (user == null)
throw new NotFoundException(nameof(User), request.UserId);
// 2. Verify با بانک
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
request.Authority,
request.Amount
);
if (!verifyResult.IsSuccess)
{
return false;
}
// 3. شارژ DiscountBalance
var wallet = await _context.UserWallets
.FirstOrDefaultAsync(w => w.UserId == user.Id, cancellationToken);
wallet.DiscountBalance += request.Amount;
// 4. ثبت Transaction
var transaction = new Transactions
{
Amount = request.Amount,
Description = "شارژ کیف پول تخفیفی",
PaymentStatus = PaymentStatus.Success,
PaymentDate = DateTime.Now,
RefId = verifyResult.RefId,
Type = TransactionType.DiscountWalletCharge
};
_context.Transactions.Add(transaction);
await _context.SaveChangesAsync(cancellationToken);
// 5. ثبت ChangeLog
var changeLog = new UserWalletChangeLog
{
UserId = user.Id,
Amount = request.Amount,
ChangeType = WalletChangeType.Deposit,
Description = "شارژ موجودی تخفیفی",
BalanceBefore = wallet.DiscountBalance - request.Amount,
BalanceAfter = wallet.DiscountBalance
};
_context.UserWalletChangeLogs.Add(changeLog);
await _context.SaveChangesAsync(cancellationToken);
return true;
}
}
📝 تسکهای پیادهسازی
Phase 1: Entity Changes (1 روز)
-
ایجاد
PackagePurchaseMethodEnum- محل:
CMSMicroservice.Domain/Enums/PackagePurchaseMethod.cs - مقادیر: None, DayaLoan, DirectPurchase
- محل:
-
اضافه کردن فیلد به
User- فیلد:
PackagePurchaseMethod PackagePurchaseMethod - مقدار پیشفرض:
PackagePurchaseMethod.None
- فیلد:
-
اضافه کردن فیلد به
ClubMembership- فیلد:
PackagePurchaseMethod PurchaseMethod
- فیلد:
-
ایجاد Migration
dotnet ef migrations add AddPackagePurchaseMethod
Phase 2: Commands (2 روز)
-
PurchaseGoldenPackageCommand- بررسی
User.PackagePurchaseMethod - ثبت
UserOrderباPackageId - Redirect به درگاه
- بررسی
-
VerifyGoldenPackagePurchaseCommand- Verify پرداخت
- شارژ
Balance - ثبت
Transaction(DepositIpg) - Set
User.PackagePurchaseMethod = DirectPurchase
-
ActivateClubMembershipCommand- چکهای امنیتی (UserOrder + Transaction)
- ثبت/بهروزرسانی
ClubMembership
-
ChargeDiscountWalletCommand+VerifyDiscountWalletChargeCommand- شارژ
DiscountBalance - ثبت
Transaction(DiscountWalletCharge)
- شارژ
Phase 3: بهروزرسانی DayaLoan Flow (0.5 روز)
- تغییر
ProcessDayaLoanCommandHandler:user.PackagePurchaseMethod = PackagePurchaseMethod.DayaLoan;
Phase 4: Unit Tests (1 روز)
-
تست
PurchaseGoldenPackageCommand:- کاربری که قبلاً پکیج خریده → باید خطا بدهد
- کاربر جدید → باید Order ایجاد شود
-
تست
ActivateClubMembershipCommand:- کاربر بدون پکیج → خطا
- کاربر با موجودی کمتر از 56M → خطا
- کاربر معتبر → موفق
-
تست
VerifyDiscountWalletChargeCommand:- پرداخت موفق →
DiscountBalanceافزایش یابد - پرداخت ناموفق → هیچ تغییری نکند
- پرداخت موفق →
Phase 5: Documentation (0.5 روز)
- بهروزرسانی
implementation-progress.md - لینک از
REMAINING-TASKS-CONSOLIDATED.md
📊 خلاصه Timeline
| Phase | عنوان | زمان |
|---|---|---|
| 1 | Entity Changes | 1 روز |
| 2 | Commands & Handlers | 2 روز |
| 3 | DayaLoan Flow Update | 0.5 روز |
| 4 | Unit Tests | 1 روز |
| 5 | Documentation | 0.5 روز |
| جمع | 5 روز |
🔗 مراجع
تاریخ آخرین بهروزرسانی: 2024-12-02
نویسنده: GitHub Copilot
وضعیت: ✅ تایید شده توسط کاربر