119e870a26
- 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
18 KiB
18 KiB
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
public enum ManualPaymentStatus
{
PendingAdminApproval = 0, // در انتظار تایید ادمین (کارتبهکارت)
PendingPayment = 1, // در انتظار پرداخت (درگاه آنلاین)
PaymentVerified = 2, // پرداخت تایید شده (از درگاه)
AdminApproved = 3, // تایید شده توسط ادمین
AdminRejected = 4, // رد شده توسط ادمین
Completed = 5, // تکمیل شده (کیفپول شارژ شده)
Failed = 6 // خطا در پردازش
}
ManualPaymentMethod Enum
public enum ManualPaymentMethod
{
OnlineGateway = 0, // درگاه آنلاین
CardToCard = 1 // کارتبهکارت
}
ManualPaymentRequest Entity
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:
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:
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:
- بررسی اینکه کاربر قبلاً درخواست Pending ندارد
- اگر Method=OnlineGateway:
- ایجاد ManualPaymentRequest با Status=PendingPayment
- فراخوانی Gateway Service برای دریافت URL پرداخت
- ذخیره GatewayName و کد درخواست
- برگرداندن PaymentUrl به کاربر
- اگر Method=CardToCard:
- آپلود تصویر رسید به Storage
- ایجاد ManualPaymentRequest با Status=PendingAdminApproval
- ذخیره UserProvidedTrackingCode و CardToCardDate
- ارسال نوتیفیکیشن به ادمینها
2. VerifyManualPaymentCommand (Callback از درگاه)
تایید پرداخت آنلاین بعد از بازگشت از درگاه
Request:
public record VerifyManualPaymentCommand : IRequest<VerifyManualPaymentResponseDto>
{
public long RequestId { get; init; }
public string GatewayTrackingCode { get; init; }
public string? Authority { get; init; } // پارامتر درگاه
}
Business Logic:
- یافتن ManualPaymentRequest با Status=PendingPayment
- فراخوانی Gateway Service برای Verify کردن تراکنش
- اگر تایید شد:
- بهروزرسانی Status → PaymentVerified
- ذخیره GatewayTrackingCode و GatewayPaymentDate
- فراخوانی ProcessManualPaymentCommand برای شارژ کیفپول
- اگر رد شد:
- بهروزرسانی Status → Failed
3. ApproveManualPaymentCommand (Admin)
تایید درخواست کارتبهکارت توسط ادمین
Request:
public record ApproveManualPaymentCommand : IRequest<ApproveManualPaymentResponseDto>
{
public long RequestId { get; init; }
public long AdminUserId { get; init; }
public string? AdminNotes { get; init; }
}
Business Logic:
- بررسی RequestId موجود با Status=PendingAdminApproval
- بررسی دسترسی ادمین
- بهروزرسانی:
- Status → AdminApproved
- ApprovedByAdminId, AdminDecisionDate, AdminNotes
- فراخوانی ProcessManualPaymentCommand برای شارژ کیفپول
4. RejectManualPaymentCommand (Admin)
رد درخواست کارتبهکارت توسط ادمین
Request:
public record RejectManualPaymentCommand : IRequest<RejectManualPaymentResponseDto>
{
public long RequestId { get; init; }
public long AdminUserId { get; init; }
public string RejectionReason { get; init; } // الزامی
}
Business Logic:
- بررسی RequestId موجود
- بهروزرسانی:
- Status → AdminRejected
- ApprovedByAdminId, AdminDecisionDate
- AdminNotes = RejectionReason
- ارسال نوتیفیکیشن به کاربر با دلیل رد
5. ProcessManualPaymentCommand (Internal)
شارژ کیفپولها بعد از تایید پرداخت
این Command داخلی است و فقط توسط Verify یا Approve فراخوانی میشود.
Business Logic:
- ایجاد Transaction:
- Type: DepositManual
- Amount: 56M
- RefId: GatewayTrackingCode یا UserProvidedTrackingCode
- شارژ Balance: +56M
- شارژ NetworkBalance: +56M
- شارژ DiscountBalance: +56M
- فعالسازی ClubMembership (اگر غیرفعال باشد)
- ثبت UserWalletChangeLog
- بهروزرسانی ManualPaymentRequest:
- Status → Completed
- TransactionId, IsProcessed=true, ProcessedDate
- ارسال نوتیفیکیشن موفقیت به کاربر
6. GetUserManualPaymentHistoryQuery
دریافت تاریخچه پرداختهای دستی کاربر
Request:
public record GetUserManualPaymentHistoryQuery : IRequest<List<ManualPaymentHistoryDto>>
{
public long UserId { get; init; }
}
7. GetPendingManualPaymentsQuery (Admin)
دریافت لیست درخواستهای در انتظار تایید
Request:
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
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: پرداخت آنلاین
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: کارتبهکارت
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: پرداخت آنلاین موفق
# 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: کارتبهکارت با تایید ادمین
# 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
- ایجاد
ManualPaymentStatusenum - ایجاد
ManualPaymentMethodenum - ایجاد
ManualPaymentRequestentity - اضافه کردن به
ApplicationDbContext
Application Layer
CreateManualPaymentRequestCommand+ Handler + ValidatorVerifyManualPaymentCommand+ HandlerApproveManualPaymentCommand+ HandlerRejectManualPaymentCommand+ HandlerProcessManualPaymentCommand+ Handler (Internal)GetUserManualPaymentHistoryQuery+ HandlerGetPendingManualPaymentsQuery+ Handler- Interface:
IPaymentGatewayService - Interface:
IFileStorageService(برای آپلود تصویر)
Infrastructure Layer
ZarinpalGatewayService: IPaymentGatewayServiceLocalFileStorageService: 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 - سیستم وام دایا
- network-club-commission-system-v1.1.md - بیزینس کلی
Created: 2024-12-01
Status: ⚠️ Not Implemented Yet (Design Complete)
Priority: High (برای کاربران بدون وام دایا ضروری است)