Files
docs/01-BUSINESS/manual-payment-system.md
T
masoodafar-web 201915d8c5 update
2025-12-05 17:28:24 +03:30

549 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 (برای کاربران بدون وام دایا ضروری است)