# Package Purchase System - سیستم خرید پکیج طلایی

**تاریخ ایجاد:** 2024-12-02  
**وضعیت:** در حال طراحی  
**اولویت:** 🔴 بسیار بالا

---

## 📋 فهرست

1. [مقدمه](#مقدمه)
2. [سه سناریوی اصلی](#سه-سناریوی-اصلی)
3. [Entity Changes](#entity-changes)
4. [Business Rules](#business-rules)
5. [Flow Diagrams](#flow-diagrams)
6. [Commands & Handlers](#commands--handlers)
7. [تسک‌های پیاده‌سازی](#تسک-های-پیاده-سازی)

---

## 🎯 مقدمه

سیستم خرید پکیج طلایی سه سناریوی مختلف دارد که باید به درستی از هم تفکیک شوند:

### هدف کلی:
- **سناریو 1 و 2**: خرید پکیج طلایی (56 میلیون تومان) → امکان فعالسازی باشگاه مشتریان
- **سناریو 3**: شارژ عادی کیف پول تخفیفی → فقط برای خرید از فروشگاه تخفیفی

### نکات کلیدی:
1. کاربر فقط **یک بار** می‌تواند پکیج طلایی خریداری کند (سناریو 1 یا 2)
2. بعد از خرید پکیج، کاربر **باید خودش** دکمه فعالسازی باشگاه را بزند
3. فعالسازی باشگاه **نیاز به تایید Admin ندارد**
4. عضویت در شبکه (NetworkMembership) **جدا** از عضویت در باشگاه (ClubMembership) است
5. کمیسیون‌ها **فقط بعد** از فعالسازی باشگاه محاسبه می‌شوند

---

## 🔄 سه سناریوی اصلی

### 📌 سناریو 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` = `DiscountWalletCharge`
- `Transaction.RefId` = کد پیگیری بانک
- **PackageId در هیچ جا ثبت نمی‌شود**
- فقط `DiscountBalance` شارژ می‌شود، نه `Balance`
- هیچ `UserOrder` با `PackageId` ثبت نمی‌شود

---

## 🗄️ Entity Changes

### 1️⃣ **Enum جدید: `PackagePurchaseMethod`**

```csharp
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**

```csharp
// اضافه کردن این فیلد به User.cs:

/// <summary>
/// نحوه خرید پکیج طلایی (برای جلوگیری از خرید مجدد)
/// </summary>
public PackagePurchaseMethod PackagePurchaseMethod { get; set; } = PackagePurchaseMethod.None;
```

**منطق:**
- وقتی کاربر سناریو 1 یا 2 را انجام می‌دهد، این فیلد تغییر می‌کند
- اگر `PackagePurchaseMethod != None` باشد، کاربر نمی‌تواند دوباره پکیج خریداری کند

---

### 3️⃣ **تغییرات `ClubMembership` Entity**

```csharp
// اضافه کردن این فیلد به ClubMembership.cs:

/// <summary>
/// نحوه خرید پکیج که منجر به فعالسازی باشگاه شد
/// </summary>
public PackagePurchaseMethod PurchaseMethod { get; set; }
```

**منطق:**
- وقتی کاربر دکمه "فعالسازی باشگاه" را می‌زند، این فیلد از `User.PackagePurchaseMethod` کپی می‌شود
- برای گزارش‌گیری و تحلیل: چند نفر از طریق وام دایا و چند نفر از طریق خرید مستقیم عضو شدند

---

### 4️⃣ **تغییرات `TransactionType` Enum**

```csharp
// فعلاً موجود است:
public enum TransactionType
{
    Buy = 0,
    DepositIpg = 1,            // پرداخت از درگاه (سناریو 2)
    DepositExternal1 = 2,      // وام دایا (سناریو 1)
    Withdraw = 3,
    NetworkCommission = 10,
    ClubActivation = 11,
    DiscountWalletCharge = 12  // شارژ کیف پول تخفیفی (سناریو 3) ✅
}
```

**نکته:** `DiscountWalletCharge` از قبل وجود دارد، پس نیازی به تغییر نیست.

---

## 📐 Business Rules

### قانون 1: یک کاربر فقط یک بار می‌تواند پکیج طلایی خریداری کند

```csharp
// Check قبل از خرید پکیج:
if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
{
    throw new ValidationException("شما قبلاً پکیج طلایی را خریداری کرده‌اید.");
}
```

---

### قانون 2: فعالسازی باشگاه فقط با موجودی اصلی (Balance) امکان‌پذیر است

```csharp
// Check موقع فعالسازی باشگاه:
var userWallet = await _context.UserWallets
    .FirstOrDefaultAsync(w => w.UserId == userId);

if (userWallet.Balance < 56_000_000)
{
    throw new ValidationException("برای فعالسازی باشگاه مشتریان باید حداقل 56 میلیون تومان موجودی اصلی داشته باشید.");
}
```

---

### قانون 3: فعالسازی باشگاه فقط برای کسانی که پکیج خریده‌اند

```csharp
// 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: محاسبه کمیسیون فقط بعد از فعالسازی باشگاه

```csharp
// در محاسبه کمیسیون:
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 به درگاه

```csharp
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 پرداخت و شارژ کیف پول

```csharp
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`

**مسئولیت:** فعالسازی عضویت در باشگاه مشتریان

```csharp
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)

**مسئولیت:** شارژ کیف پول تخفیفی

```csharp
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

```csharp
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 روز)

1. **ایجاد `PackagePurchaseMethod` Enum**
   - محل: `CMSMicroservice.Domain/Enums/PackagePurchaseMethod.cs`
   - مقادیر: None, DayaLoan, DirectPurchase

2. **اضافه کردن فیلد به `User`**
   - فیلد: `PackagePurchaseMethod PackagePurchaseMethod`
   - مقدار پیش‌فرض: `PackagePurchaseMethod.None`

3. **اضافه کردن فیلد به `ClubMembership`**
   - فیلد: `PackagePurchaseMethod PurchaseMethod`

4. **ایجاد Migration**
   ```bash
   dotnet ef migrations add AddPackagePurchaseMethod
   ```

---

### Phase 2: Commands (2 روز)

1. **`PurchaseGoldenPackageCommand`**
   - بررسی `User.PackagePurchaseMethod`
   - ثبت `UserOrder` با `PackageId`
   - Redirect به درگاه

2. **`VerifyGoldenPackagePurchaseCommand`**
   - Verify پرداخت
   - شارژ `Balance`
   - ثبت `Transaction` (DepositIpg)
   - Set `User.PackagePurchaseMethod = DirectPurchase`

3. **`ActivateClubMembershipCommand`**
   - چک‌های امنیتی (UserOrder + Transaction)
   - ثبت/به‌روزرسانی `ClubMembership`

4. **`ChargeDiscountWalletCommand` + `VerifyDiscountWalletChargeCommand`**
   - شارژ `DiscountBalance`
   - ثبت `Transaction` (DiscountWalletCharge)

---

### Phase 3: به‌روزرسانی DayaLoan Flow (0.5 روز)

- تغییر `ProcessDayaLoanCommandHandler`:
  ```csharp
  user.PackagePurchaseMethod = PackagePurchaseMethod.DayaLoan;
  ```

---

### Phase 4: Unit Tests (1 روز)

1. تست `PurchaseGoldenPackageCommand`:
   - کاربری که قبلاً پکیج خریده → باید خطا بدهد
   - کاربر جدید → باید Order ایجاد شود

2. تست `ActivateClubMembershipCommand`:
   - کاربر بدون پکیج → خطا
   - کاربر با موجودی کمتر از 56M → خطا
   - کاربر معتبر → موفق

3. تست `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 روز** |

---

## 🔗 مراجع

- [DayaLoan Integration](./daya-loan-integration.md)
- [Manual Payment System](./manual-payment-system.md)
- [Implementation Progress](./implementation-progress.md)
- [REMAINING-TASKS](../REMAINING-TASKS-CONSOLIDATED.md)

---

**تاریخ آخرین به‌روزرسانی:** 2024-12-02  
**نویسنده:** GitHub Copilot  
**وضعیت:** ✅ تایید شده توسط کاربر
