# Manual Payment System (سیستم پرداخت دستی مشتریان)

## 📌 Overview

سیستم پرداخت دستی برای مشتریانی که **بدون خرید وام دایا** می‌خواهند مستقیماً 56 میلیون تومان پرداخت کنند و همان مزایا را دریافت کنند.

### 🎯 سناریوها

#### سناریو 1: پرداخت آنلاین (درگاه پرداخت)
```
کاربر → انتخاب گزینه "پرداخت دستی" در فرانت‌آفیس
  ↓
ایجاد Transaction با Type=ManualPaymentOnline, Amount=56M, Status=Pending
  ↓
ریدایرکت به درگاه پرداخت (Zarinpal/Mellat/...)
  ↓
Callback از درگاه با RefId
  ↓
VerifyManualPaymentCommand → تایید تراکنش
  ↓
شارژ کیف‌پول‌ها (Balance=56M, NetworkBalance=56M, DiscountBalance=56M)
  ↓
فعال‌سازی عضویت باشگاه (ClubMembership)
```

#### سناریو 2: کارت‌به‌کارت با تایید ادمین
```
کاربر → کارت‌به‌کارت 56 میلیون + ارسال تصویر رسید
  ↓
CreateManualPaymentRequestCommand → ثبت درخواست با Status=PendingAdminApproval
  - تصویر رسید + کد پیگیری استخراج شده توسط کاربر
  ↓
ادمین → بررسی درخواست در BackOffice
  ↓
ApproveManualPaymentCommand یا RejectManualPaymentCommand
  ↓
در صورت تایید: 
  - ایجاد Transaction با RefId=کد پیگیری
  - شارژ کیف‌پول‌ها
  - فعال‌سازی عضویت باشگاه
  ↓
در صورت رد:
  - ثبت دلیل رد
  - اطلاع‌رسانی به کاربر
```

---

## 🗂️ Architecture

### Domain Layer

> **وضعیت فعلی پیاده‌سازی (CMS)**  
> در نسخه‌ای که الآن در CMS داریم، سناریوی «درخواست پرداخت دستی توسط کاربر» (ManualPaymentRequest + Verify از درگاه) هنوز پیاده‌سازی نشده و فقط بخش **پرداخت دستی توسط Admin/SuperAdmin** با Entity ساده‌تر `ManualPayment` و Enumهای `ManualPaymentType` و `ManualPaymentStatus` (Pending/Approved/Rejected/Cancelled) اجرا شده است.  
> بخش‌های زیر که با `ManualPaymentRequest`، `ManualPaymentMethod` و Verify/ProcessManualPayment توضیح داده شده‌اند، طراحی کامل سیستم هستند و برای فاز بعدی (FrontOffice + OnlineGateway/CardToCard) استفاده خواهند شد.

#### **ManualPaymentStatus Enum (طراحی کامل – برای Requestها)**
```csharp
public enum ManualPaymentStatus
{
    PendingAdminApproval = 0,  // در انتظار تایید ادمین (کارت‌به‌کارت)
    PendingPayment = 1,         // در انتظار پرداخت (درگاه آنلاین)
    PaymentVerified = 2,        // پرداخت تایید شده (از درگاه)
    AdminApproved = 3,          // تایید شده توسط ادمین
    AdminRejected = 4,          // رد شده توسط ادمین
    Completed = 5,              // تکمیل شده (کیف‌پول شارژ شده)
    Failed = 6                  // خطا در پردازش
}
```

#### **ManualPaymentMethod Enum**
```csharp
public enum ManualPaymentMethod
{
    OnlineGateway = 0,     // درگاه آنلاین
    CardToCard = 1         // کارت‌به‌کارت
}
```

#### **ManualPaymentRequest Entity (طراحی کامل – هنوز پیاده نشده)**
```csharp
public class ManualPaymentRequest : BaseAuditableEntity
{
    public long UserId { get; set; }
    public ManualPaymentMethod Method { get; set; }
    public ManualPaymentStatus Status { get; set; }
    public long Amount { get; set; } = 56_000_000; // مبلغ ثابت
    
    // آنلاین Gateway
    public string? GatewayName { get; set; }        // Zarinpal, Mellat, etc.
    public string? GatewayTrackingCode { get; set; } // کد پیگیری درگاه
    public DateTime? GatewayPaymentDate { get; set; }
    
    // کارت‌به‌کارت
    public string? ReceiptImageUrl { get; set; }     // مسیر تصویر رسید
    public string? UserProvidedTrackingCode { get; set; } // کد پیگیری که کاربر داده
    public DateTime? CardToCardDate { get; set; }
    
    // تایید/رد ادمین
    public long? ApprovedByAdminId { get; set; }
    public DateTime? AdminDecisionDate { get; set; }
    public string? AdminNotes { get; set; }         // توضیحات ادمین (دلیل رد)
    
    // تراکنش نهایی
    public long? TransactionId { get; set; }
    public bool IsProcessed { get; set; }
    public DateTime? ProcessedDate { get; set; }
    
    // Navigation Properties
    public virtual User User { get; set; }
    public virtual User? ApprovedByAdmin { get; set; }
    public virtual Transactions? Transaction { get; set; }
}
```

---

### Application Layer

#### **Commands**

##### 1. CreateManualPaymentRequestCommand (FrontOffice)
ایجاد درخواست پرداخت دستی توسط کاربر

**Request:**
```csharp
public record CreateManualPaymentRequestCommand : IRequest<CreateManualPaymentRequestResponseDto>
{
    public long UserId { get; init; }
    public ManualPaymentMethod Method { get; init; }
    
    // برای OnlineGateway
    public string? GatewayName { get; init; }
    public string? ReturnUrl { get; init; } // URL بازگشت بعد از پرداخت
    
    // برای CardToCard
    public IFormFile? ReceiptImage { get; init; }     // فایل تصویر رسید
    public string? TrackingCode { get; init; }         // کد پیگیری
    public DateTime? TransactionDate { get; init; }
}
```

**Response:**
```csharp
public class CreateManualPaymentRequestResponseDto
{
    public long RequestId { get; set; }
    public ManualPaymentStatus Status { get; set; }
    
    // برای OnlineGateway: URL پرداخت
    public string? PaymentUrl { get; set; }
    
    // برای CardToCard: پیام موفقیت
    public string Message { get; set; }
}
```

**Business Logic:**
1. بررسی اینکه کاربر قبلاً درخواست Pending ندارد
2. اگر Method=OnlineGateway:
   - ایجاد ManualPaymentRequest با Status=PendingPayment
   - فراخوانی Gateway Service برای دریافت URL پرداخت
   - ذخیره GatewayName و کد درخواست
   - برگرداندن PaymentUrl به کاربر
3. اگر Method=CardToCard:
   - آپلود تصویر رسید به Storage
   - ایجاد ManualPaymentRequest با Status=PendingAdminApproval
   - ذخیره UserProvidedTrackingCode و CardToCardDate
   - ارسال نوتیفیکیشن به ادمین‌ها

##### 2. VerifyManualPaymentCommand (Callback از درگاه)
تایید پرداخت آنلاین بعد از بازگشت از درگاه

**Request:**
```csharp
public record VerifyManualPaymentCommand : IRequest<VerifyManualPaymentResponseDto>
{
    public long RequestId { get; init; }
    public string GatewayTrackingCode { get; init; }
    public string? Authority { get; init; } // پارامتر درگاه
}
```

**Business Logic:**
1. یافتن ManualPaymentRequest با Status=PendingPayment
2. فراخوانی Gateway Service برای Verify کردن تراکنش
3. اگر تایید شد:
   - به‌روزرسانی Status → PaymentVerified
   - ذخیره GatewayTrackingCode و GatewayPaymentDate
   - فراخوانی ProcessManualPaymentCommand برای شارژ کیف‌پول
4. اگر رد شد:
   - به‌روزرسانی Status → Failed

##### 3. ApproveManualPaymentCommand (Admin)
تایید درخواست کارت‌به‌کارت توسط ادمین

**Request:**
```csharp
public record ApproveManualPaymentCommand : IRequest<ApproveManualPaymentResponseDto>
{
    public long RequestId { get; init; }
    public long AdminUserId { get; init; }
    public string? AdminNotes { get; init; }
}
```

**Business Logic:**
1. بررسی RequestId موجود با Status=PendingAdminApproval
2. بررسی دسترسی ادمین
3. به‌روزرسانی:
   - Status → AdminApproved
   - ApprovedByAdminId, AdminDecisionDate, AdminNotes
4. فراخوانی ProcessManualPaymentCommand برای شارژ کیف‌پول

##### 4. RejectManualPaymentCommand (Admin)
رد درخواست کارت‌به‌کارت توسط ادمین

**Request:**
```csharp
public record RejectManualPaymentCommand : IRequest<RejectManualPaymentResponseDto>
{
    public long RequestId { get; init; }
    public long AdminUserId { get; init; }
    public string RejectionReason { get; init; } // الزامی
}
```

**Business Logic:**
1. بررسی RequestId موجود
2. به‌روزرسانی:
   - Status → AdminRejected
   - ApprovedByAdminId, AdminDecisionDate
   - AdminNotes = RejectionReason
3. ارسال نوتیفیکیشن به کاربر با دلیل رد

##### 5. ProcessManualPaymentCommand (Internal)
شارژ کیف‌پول‌ها بعد از تایید پرداخت

**این Command داخلی است و فقط توسط Verify یا Approve فراخوانی می‌شود.**

**Business Logic:**
1. ایجاد Transaction:
   - Type: DepositManual
   - Amount: 56M
   - RefId: GatewayTrackingCode یا UserProvidedTrackingCode
2. شارژ Balance: +56M
3. شارژ NetworkBalance: +56M
4. شارژ DiscountBalance: +56M
5. فعال‌سازی ClubMembership (اگر غیرفعال باشد)
6. ثبت UserWalletChangeLog
7. به‌روزرسانی ManualPaymentRequest:
   - Status → Completed
   - TransactionId, IsProcessed=true, ProcessedDate
8. ارسال نوتیفیکیشن موفقیت به کاربر

##### 6. GetUserManualPaymentHistoryQuery
دریافت تاریخچه پرداخت‌های دستی کاربر

**Request:**
```csharp
public record GetUserManualPaymentHistoryQuery : IRequest<List<ManualPaymentHistoryDto>>
{
    public long UserId { get; init; }
}
```

##### 7. GetPendingManualPaymentsQuery (Admin)
دریافت لیست درخواست‌های در انتظار تایید

**Request:**
```csharp
public record GetPendingManualPaymentsQuery : IRequest<List<PendingManualPaymentDto>>
{
    public ManualPaymentStatus? StatusFilter { get; init; } = ManualPaymentStatus.PendingAdminApproval;
    public int PageNumber { get; init; } = 1;
    public int PageSize { get; init; } = 20;
}
```

---

## 💾 Database Schema

### ManualPaymentRequests Table
```sql
CREATE TABLE [CMS].[ManualPaymentRequests] (
    [Id] bigint IDENTITY(1,1) PRIMARY KEY,
    [UserId] bigint NOT NULL FOREIGN KEY REFERENCES Users(Id),
    [Method] int NOT NULL,
    [Status] int NOT NULL,
    [Amount] bigint NOT NULL DEFAULT 56000000,
    
    -- آنلاین Gateway
    [GatewayName] nvarchar(50) NULL,
    [GatewayTrackingCode] nvarchar(200) NULL,
    [GatewayPaymentDate] datetime2 NULL,
    
    -- کارت‌به‌کارت
    [ReceiptImageUrl] nvarchar(500) NULL,
    [UserProvidedTrackingCode] nvarchar(200) NULL,
    [CardToCardDate] datetime2 NULL,
    
    -- تایید ادمین
    [ApprovedByAdminId] bigint NULL FOREIGN KEY REFERENCES Users(Id),
    [AdminDecisionDate] datetime2 NULL,
    [AdminNotes] nvarchar(max) NULL,
    
    -- تراکنش
    [TransactionId] bigint NULL FOREIGN KEY REFERENCES Transactionss(Id),
    [IsProcessed] bit NOT NULL DEFAULT 0,
    [ProcessedDate] datetime2 NULL,
    
    -- Audit
    [Created] datetime2 NOT NULL,
    [CreatedBy] nvarchar(max) NULL,
    [LastModified] datetime2 NULL,
    [LastModifiedBy] nvarchar(max) NULL,
    [IsDeleted] bit NOT NULL DEFAULT 0
);

CREATE INDEX IX_ManualPaymentRequests_UserId ON ManualPaymentRequests(UserId);
CREATE INDEX IX_ManualPaymentRequests_Status ON ManualPaymentRequests(Status);
CREATE INDEX IX_ManualPaymentRequests_TransactionId ON ManualPaymentRequests(TransactionId);
```

---

## 🔄 Process Flows

### Flow 1: پرداخت آنلاین
```mermaid
sequenceDiagram
    participant User as کاربر
    participant FrontOffice as FrontOffice
    participant CMS as CMS API
    participant Gateway as درگاه پرداخت
    
    User->>FrontOffice: انتخاب "پرداخت دستی"
    FrontOffice->>CMS: CreateManualPaymentRequest (Method=OnlineGateway)
    CMS->>Gateway: ایجاد درخواست پرداخت
    Gateway-->>CMS: PaymentUrl
    CMS-->>FrontOffice: PaymentUrl
    FrontOffice->>Gateway: ریدایرکت کاربر
    User->>Gateway: پرداخت 56M
    Gateway->>CMS: Callback (RefId, Authority)
    CMS->>Gateway: Verify Payment
    Gateway-->>CMS: تایید پرداخت
    CMS->>CMS: ProcessManualPayment (شارژ کیف‌پول)
    CMS-->>FrontOffice: موفقیت
    FrontOffice-->>User: پرداخت موفق
```

### Flow 2: کارت‌به‌کارت
```mermaid
sequenceDiagram
    participant User as کاربر
    participant FrontOffice as FrontOffice
    participant CMS as CMS API
    participant Admin as ادمین (BackOffice)
    
    User->>User: کارت‌به‌کارت 56M
    User->>FrontOffice: آپلود رسید + کد پیگیری
    FrontOffice->>CMS: CreateManualPaymentRequest (Method=CardToCard)
    CMS->>CMS: ذخیره تصویر + Status=PendingAdminApproval
    CMS-->>Admin: نوتیفیکیشن (درخواست جدید)
    Admin->>CMS: GetPendingManualPayments
    CMS-->>Admin: لیست درخواست‌ها
    Admin->>Admin: بررسی رسید و کد پیگیری
    
    alt تایید
        Admin->>CMS: ApproveManualPayment
        CMS->>CMS: ProcessManualPayment (شارژ کیف‌پول)
        CMS-->>User: نوتیفیکیشن موفقیت
    else رد
        Admin->>CMS: RejectManualPayment (دلیل رد)
        CMS-->>User: نوتیفیکیشن رد با دلیل
    end
```

---

## 🧪 Testing Scenarios

### Test 1: پرداخت آنلاین موفق
```bash
# Step 1: ایجاد درخواست
POST /api/manualpayment/create
{
  "userId": 123,
  "method": 0,
  "gatewayName": "Zarinpal",
  "returnUrl": "https://example.com/callback"
}

# Response: PaymentUrl

# Step 2: کاربر پرداخت می‌کند (Mock Gateway)

# Step 3: Callback
POST /api/manualpayment/verify
{
  "requestId": 456,
  "gatewayTrackingCode": "ZP-12345",
  "authority": "A00000000..."
}

# Result: کیف‌پول شارژ شده، باشگاه فعال
```

### Test 2: کارت‌به‌کارت با تایید ادمین
```bash
# Step 1: ایجاد درخواست کاربر
POST /api/manualpayment/create
{
  "userId": 123,
  "method": 1,
  "receiptImage": <file>,
  "trackingCode": "REF-98765",
  "transactionDate": "2024-12-01T10:00:00Z"
}

# Step 2: ادمین بررسی می‌کند
GET /api/admin/manualpayment/pending

# Step 3: ادمین تایید می‌کند
POST /api/admin/manualpayment/approve
{
  "requestId": 456,
  "adminUserId": 1,
  "adminNotes": "رسید معتبر است"
}

# Result: کیف‌پول شارژ شده
```

---

## 📋 Implementation Tasks

### CMS Microservice

#### Domain Layer
- [ ] ایجاد `ManualPaymentStatus` enum
- [ ] ایجاد `ManualPaymentMethod` enum
- [ ] ایجاد `ManualPaymentRequest` entity
- [ ] اضافه کردن به `ApplicationDbContext`

#### Application Layer
- [ ] `CreateManualPaymentRequestCommand` + Handler + Validator
- [ ] `VerifyManualPaymentCommand` + Handler
- [ ] `ApproveManualPaymentCommand` + Handler
- [ ] `RejectManualPaymentCommand` + Handler
- [ ] `ProcessManualPaymentCommand` + Handler (Internal)
- [ ] `GetUserManualPaymentHistoryQuery` + Handler
- [ ] `GetPendingManualPaymentsQuery` + Handler
- [ ] Interface: `IPaymentGatewayService`
- [ ] Interface: `IFileStorageService` (برای آپلود تصویر)

#### Infrastructure Layer
- [ ] `ZarinpalGatewayService` : IPaymentGatewayService
- [ ] `LocalFileStorageService` : IFileStorageService
- [ ] Migration: `AddManualPaymentSystem`

#### WebApi Layer (Protobuf/gRPC)
- [ ] Proto definitions: `ManualPayment.proto`
- [ ] gRPC Service: `ManualPaymentService`

### FrontOffice

#### Components
- [ ] `ManualPaymentPage.razor` - صفحه انتخاب روش پرداخت
- [ ] `OnlinePaymentForm.razor` - فرم پرداخت آنلاین
- [ ] `CardToCardForm.razor` - فرم کارت‌به‌کارت (آپلود رسید)
- [ ] `PaymentCallbackPage.razor` - صفحه بازگشت از درگاه
- [ ] `PaymentHistoryPage.razor` - تاریخچه پرداخت‌های کاربر

#### Services
- [ ] `ManualPaymentService.cs` - فراخوانی BFF

### FrontOffice.BFF

#### Application Layer
- [ ] CQRS Handlers برای مپ کردن gRPC به REST
- [ ] DTOs برای API های REST

#### WebApi Layer
- [ ] `ManualPaymentController.cs` - REST endpoints

### BackOffice

#### Components
- [ ] `PendingPaymentsPage.razor` - لیست درخواست‌های در انتظار
- [ ] `PaymentRequestDetailsModal.razor` - جزئیات + نمایش رسید
- [ ] `ApproveRejectButtons.razor` - دکمه‌های تایید/رد

#### Services
- [ ] `ManualPaymentAdminService.cs` - فراخوانی BFF

### BackOffice.BFF

#### Application Layer
- [ ] Admin CQRS Handlers
- [ ] Admin DTOs

#### WebApi Layer
- [ ] `AdminManualPaymentController.cs` - REST endpoints برای ادمین

---

## ⚠️ Important Notes

### 1. Transaction Type
- برای پرداخت دستی از `TransactionType.DepositManual` استفاده شود
- RefId = GatewayTrackingCode (آنلاین) یا UserProvidedTrackingCode (کارت‌به‌کارت)

### 2. Security
- تایید پرداخت درگاه باید با Signature Verification انجام شود
- تصاویر رسید باید با Validation بارگذاری شوند (حجم، فرمت، محتوا)
- فقط ادمین‌ها حق تایید/رد کارت‌به‌کارت دارند

### 3. Idempotency
- نباید کاربر بتواند چند درخواست همزمان Pending داشته باشد
- هر RequestId فقط یک بار قابل Verify است

### 4. Notifications
- SMS/Email به کاربر بعد از:
  - ایجاد درخواست کارت‌به‌کارت
  - تایید/رد ادمین
  - موفقیت پرداخت آنلاین

### 5. File Storage
- تصاویر رسید باید با GUID ذخیره شوند
- مسیر: `/uploads/receipts/{year}/{month}/{guid}.jpg`
- حداکثر حجم: 2MB
- فرمت‌های مجاز: JPG, PNG, PDF

---

## 🔗 Related Documentation

- [daya-loan-integration.md](./daya-loan-integration.md) - سیستم وام دایا
- [network-club-commission-system-v1.1.md](./network-club-commission-system-v1.1.md) - بیزینس کلی

---

**Created:** 2024-12-01  
**Status:** ⚠️ Not Implemented Yet (Design Complete)  
**Priority:** High (برای کاربران بدون وام دایا ضروری است)
