# 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 #### **ManualPaymentStatus Enum** ```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 { 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 { 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 { 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 { 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> { public long UserId { get; init; } } ``` ##### 7. GetPendingManualPaymentsQuery (Admin) دریافت لیست درخواست‌های در انتظار تایید **Request:** ```csharp public record GetPendingManualPaymentsQuery : IRequest> { 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": , "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 (برای کاربران بدون وام دایا ضروری است)