# Base Package Payment System - سیستم پرداخت پکیج پایه **تاریخ ایجاد:** 2024-12-16 **تاریخ آخرین به‌روزرسانی:** 2024-12-16 **وضعیت:** ✅ پیاده‌سازی شده **اولویت:** 🔴 بسیار بالا --- ## 📋 فهرست 1. [خلاصه سیستم](#خلاصه-سیستم) 2. [Business Requirements](#business-requirements) 3. [معماری سیستم](#معماری-سیستم) 4. [Implementation Details](#implementation-details) 5. [Club Membership Contract System](#club-membership-contract-system) 6. [API Endpoints](#api-endpoints) 7. [Flow Diagram](#flow-diagram) 8. [نکات مهم](#نکات-مهم) --- ## 🎯 خلاصه سیستم سیستم پرداخت پکیج پایه امکان پرداخت **56 میلیون تومان** را برای کاربران فراهم می‌کند تا بتوانند: 1. کیف پول خود را شارژ کنند (Balance + DiscountBalance) 2. **امضای قرارداد باشگاه مشتریان** (گام الزامی بعد از پرداخت) 3. **فعالسازی لینک دعوت** (Referral Link) - تنها بعد از امضای قرارداد 4. دسترسی کامل به امکانات باشگاه مشتریان ### دو روش پرداخت: 1. **پرداخت مستقیم (Direct Payment)** - از طریق درگاه بانکی (زرین‌پال) 2. **اعتبار الماسی دایا (Daya Loan)** - از طریق سایت دایا --- ## 📊 Business Requirements ### شرایط نمایش لینک دعوت: ``` CanShowReferralLink = HasPurchasedPackage && IsClubMemberActive ``` - **HasPurchasedPackage**: کاربر پکیج پایه را خریداری کرده (PackagePurchaseMethod != None) - **IsClubMemberActive**: قرارداد باشگاه مشتریان امضا شده (ClubMembership.IsActive = true) ⚠️ **نکته مهم**: پرداخت پکیج به تنهایی کافی نیست! کاربر باید قرارداد باشگاه مشتریان را نیز امضا کند. ### مقدار پکیج: - **مبلغ**: 56,000,000 تومان - **شارژ Balance**: 56,000,000 تومان - **شارژ DiscountBalance**: 56,000,000 تومان ### PackagePurchaseMethod Enum: ```csharp public enum PackagePurchaseMethod { None = 0, // هنوز خرید نکرده DirectPurchase = 1, // پرداخت مستقیم DayaLoan = 2 // اعتبار دایا } ``` ### ContractType Enum: ```csharp public enum ContractType { Main = 0, // قرارداد ثبت‌نام اولیه ClubMembership = 1, // قرارداد باشگاه مشتریان } ``` --- ## 🏗️ معماری سیستم ### Architecture Pattern: ``` Frontend (Blazor) ↓ BFF (Backend For Frontend) ↓ ↘ CMS PYMS (Payment Gateway) ``` ### Layer Responsibilities: #### 1️⃣ Frontend (Blazor) - نمایش UI برای انتخاب روش پرداخت - فراخوانی BFF برای شروع پرداخت - مدیریت Callback از درگاه - نمایش نتیجه پرداخت - **Modal غیرقابل بسته شدن برای امضای قرارداد باشگاه** (جدید ✨) #### 2️⃣ BFF (Middle Layer) - **InitiateBasePackagePayment**: هماهنگی بین CMS و PYMS - فراخوانی CMS برای ثبت Transaction + Order - فراخوانی PYMS برای دریافت URL درگاه - برگرداندن URL به Frontend - **VerifyBasePackagePayment**: تأیید پرداخت - فراخوانی PYMS برای Verify - فراخوانی CMS برای شارژ یا Reject - **RequestClubContractOtp**: ارسال OTP برای امضای قرارداد (جدید ✨) - **AcceptClubMembershipContract**: امضای قرارداد و فعالسازی باشگاه (جدید ✨) #### 3️⃣ CMS (Core Business) - **InitiateBasePackagePayment**: ثبت Transaction + Order با Pending - **VerifyBasePackagePayment**: شارژ کیف پول یا Reject بر اساس نتیجه - **AcceptClubMembershipContract**: ثبت UserContract و فعالسازی ClubMembership (جدید ✨) #### 4️⃣ PYMS (Payment Gateway Service) - **PaymentRequest**: دریافت URL درگاه زرین‌پال - **PaymentVerification**: تأیید پرداخت از بانک --- ## 💻 Implementation Details ### CMS Layer #### Commands: 1. **InitiateBasePackagePaymentCommand** ```csharp // Input public record InitiateBasePackagePaymentCommand { public long UserId { get; init; } } // Output public class InitiateBasePackagePaymentResponseDto { public bool Success { get; set; } public string Message { get; set; } public long OrderId { get; set; } public long TransactionId { get; set; } public long Amount { get; set; } // 56,000,000 } ``` **Handler Logic:** - بررسی عدم خرید قبلی: `user.PackagePurchaseMethod == None` - بررسی عدم Order Pending قبلی - ایجاد Transaction با PaymentStatus.Pending - ایجاد UserOrder با PackageId=4, PaymentStatus.Pending - Return OrderId + TransactionId 2. **VerifyBasePackagePaymentCommand** ```csharp // Input public record VerifyBasePackagePaymentCommand { public long OrderId { get; init; } public long TransactionId { get; init; } public bool PaymentSuccess { get; init; } // از BFF می‌آید public string? RefId { get; init; } public string? Message { get; init; } } // Output public class VerifyBasePackagePaymentResponseDto { public bool Success { get; set; } public string Message { get; set; } public long OrderId { get; set; } public long TransactionId { get; set; } public string? ReferenceCode { get; set; } public long WalletBalance { get; set; } public long DiscountBalance { get; set; } } ``` **Handler Logic (Success):** - شارژ `wallet.Balance += 56,000,000` - شارژ `wallet.DiscountBalance += 56,000,000` - ثبت Transaction با PaymentStatus.Success - ثبت UserWalletChangeLog (Balance + Discount) - Update Order: PaymentStatus.Success, PaymentMethod.IPG - Update User: PackagePurchaseMethod.DirectPurchase **Handler Logic (Failed):** - Update Transaction: PaymentStatus.Reject - Update Order: PaymentStatus.Reject #### Proto Definition: ```protobuf // package.proto service PackageContract { rpc InitiateBasePackagePayment(InitiateBasePackagePaymentRequest) returns (InitiateBasePackagePaymentResponse); rpc VerifyBasePackagePayment(VerifyBasePackagePaymentRequest) returns (VerifyBasePackagePaymentResponse); } message InitiateBasePackagePaymentRequest { int64 user_id = 1; } message InitiateBasePackagePaymentResponse { bool success = 1; string message = 2; int64 order_id = 3; int64 transaction_id = 4; int64 amount = 5; } message VerifyBasePackagePaymentRequest { int64 order_id = 1; int64 transaction_id = 2; bool payment_success = 3; google.protobuf.StringValue ref_id = 4; google.protobuf.StringValue message = 5; } message VerifyBasePackagePaymentResponse { bool success = 1; string message = 2; int64 order_id = 3; int64 transaction_id = 4; google.protobuf.StringValue reference_code = 5; int64 wallet_balance = 6; int64 discount_balance = 7; } ``` #### Files Created/Modified: ``` CMS/src/CMSMicroservice.Application/PackageCQ/Commands/ ├── InitiateBasePackagePayment/ │ ├── InitiateBasePackagePaymentCommand.cs │ ├── InitiateBasePackagePaymentCommandValidator.cs │ └── InitiateBasePackagePaymentCommandHandler.cs └── VerifyBasePackagePayment/ ├── VerifyBasePackagePaymentCommand.cs ├── VerifyBasePackagePaymentCommandValidator.cs └── VerifyBasePackagePaymentCommandHandler.cs CMS/src/CMSMicroservice.Protobuf/Protos/ └── package.proto (updated) CMS/src/CMSMicroservice.WebApi/ ├── Services/PackageService.cs (updated) └── Common/Mappings/PackageProfile.cs (updated) ``` --- ### BFF Layer #### Commands: 1. **InitiateBasePackagePaymentCommand** ```csharp // Input (UserId از CurrentUserService گرفته می‌شود) public record InitiateBasePackagePaymentCommand { public string CallbackUrl { get; init; } } // Output public class InitiateBasePackagePaymentResponseDto { public bool Success { get; set; } public string Message { get; set; } public long OrderId { get; set; } public long TransactionId { get; set; } public long Amount { get; set; } public string PaymentGatewayUrl { get; set; } public string Authority { get; set; } } ``` **Handler Logic:** ```csharp // 1. فراخوانی CMS var cmsResponse = await _context.Package.InitiateBasePackagePaymentAsync( new InitiateBasePackagePaymentRequest { UserId = _currentUserService.UserId.Value }); // 2. فراخوانی PYMS var paymentResponse = await _context.ZarinTransactions.PaymentRequestAsync( new PaymentRequestRequest { MerchantId = "...", Amount = cmsResponse.Amount * 10, // تبدیل به ریال CallbackUrl = $"{request.CallbackUrl}?orderId={...}&transactionId={...}", Description = "پرداخت پکیج پایه", Currency = CurrencyEnum.Irr, Type = TransactionTypeEnum.Real }); // 3. Return URL + Authority return new InitiateBasePackagePaymentResponseDto { PaymentGatewayUrl = paymentResponse.PaymentGWUrl, Authority = ExtractAuthorityFromUrl(paymentResponse.PaymentGWUrl), ... }; ``` 2. **VerifyBasePackagePaymentCommand** ```csharp // Input public record VerifyBasePackagePaymentCommand { public long OrderId { get; init; } public long TransactionId { get; init; } public string Authority { get; init; } public string Status { get; init; } // OK یا NOK } ``` **Handler Logic:** ```csharp // 1. بررسی Status if (request.Status != "OK") { await NotifyCmsPaymentFailed(...); return Failed; } // 2. Verify از PYMS var verifyResponse = await _context.ZarinTransactions .PaymentVerificationAsync(...); // 3. فراخوانی CMS if (verifyResponse.PaymentStatus) { var cmsResponse = await _context.Package.VerifyBasePackagePaymentAsync( new VerifyBasePackagePaymentRequest { OrderId = request.OrderId, TransactionId = request.TransactionId, PaymentSuccess = true, RefId = verifyResponse.RefId, Message = verifyResponse.Message }); return Success; } else { await NotifyCmsPaymentFailed(...); return Failed; } ``` #### Proto Definition: ```protobuf // package.proto service PackageContract { rpc InitiateBasePackagePayment(InitiateBasePackagePaymentRequest) returns (InitiateBasePackagePaymentResponse) { option (google.api.http) = { post: "/InitiateBasePackagePayment" body: "*" }; }; rpc VerifyBasePackagePayment(VerifyBasePackagePaymentRequest) returns (VerifyBasePackagePaymentResponse) { option (google.api.http) = { post: "/VerifyBasePackagePayment" body: "*" }; }; } message InitiateBasePackagePaymentRequest { string callback_url = 1; // UserId از JWT token گرفته می‌شود } message InitiateBasePackagePaymentResponse { bool success = 1; string message = 2; int64 order_id = 3; int64 transaction_id = 4; int64 amount = 5; string payment_gateway_url = 6; string authority = 7; } message VerifyBasePackagePaymentRequest { int64 order_id = 1; int64 transaction_id = 2; string authority = 3; string status = 4; } message VerifyBasePackagePaymentResponse { bool success = 1; string message = 2; int64 order_id = 3; int64 transaction_id = 4; google.protobuf.StringValue ref_id = 5; int64 wallet_balance = 6; int64 discount_balance = 7; } ``` #### Files Created/Modified: ``` FrontOffice.BFF/src/FrontOffice.BFF.Application/PackageCQ/Commands/ ├── InitiateBasePackagePayment/ │ ├── InitiateBasePackagePaymentCommand.cs │ ├── InitiateBasePackagePaymentCommandValidator.cs │ └── InitiateBasePackagePaymentCommandHandler.cs └── VerifyBasePackagePayment/ ├── VerifyBasePackagePaymentCommand.cs ├── VerifyBasePackagePaymentCommandValidator.cs └── VerifyBasePackagePaymentCommandHandler.cs FrontOffice.BFF/src/Protobufs/FrontOffice.BFF.Package.Protobuf/Protos/ └── package.proto (updated) FrontOffice.BFF/src/FrontOffice.BFF.WebApi/ ├── Services/PackageService.cs (updated) └── Common/Mappings/PackageProfile.cs (updated) FrontOffice.BFF/src/FrontOffice.BFF.Domain/ └── FrontOffice.BFF.Domain.csproj (updated - added CMS Proto reference) ``` --- ### Frontend Layer #### Pages: 1. **Profile/Index.razor.cs** - نمایش دکمه "خرید پکیج پایه" - Bottom Sheet با دو گزینه: پرداخت مستقیم / اعتبار الماسی - فراخوانی BFF.InitiateBasePackagePayment ```csharp private async Task DirectPayment() { var callbackUrl = $"{Navigation.BaseUri}profile/payment-callback"; var response = await PackageContract.InitiateBasePackagePaymentAsync( new InitiateBasePackagePaymentRequest { CallbackUrl = callbackUrl }); if (response.Success) { Navigation.NavigateTo(response.PaymentGatewayUrl, forceLoad: true); } } ``` 2. **Profile/PaymentCallback.razor** - دریافت Query Parameters: orderId, transactionId, Authority, Status - فراخوانی BFF.VerifyBasePackagePayment - نمایش نتیجه (موفق/ناموفق) ```csharp protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { var response = await PackageContract.VerifyBasePackagePaymentAsync( new VerifyBasePackagePaymentRequest { OrderId = OrderId, TransactionId = TransactionId, Authority = Authority, Status = Status }); // نمایش نتیجه } } ``` #### Files Created/Modified: ``` FrontOffice/src/FrontOffice.Main/Pages/Profile/ ├── Index.razor.cs (updated) └── PaymentCallback.razor (new) FrontOffice/src/FrontOffice.Main/Utilities/ ├── UserAuthInfo.cs (updated - added UserId) └── AuthService.cs (updated - extract UserId from JWT) FrontOffice/src/FrontOffice.Main/ └── FrontOffice.Main.csproj (updated - added BFF Package Proto reference) ``` --- ## 🔌 API Endpoints ### BFF Endpoints (gRPC-Web + HTTP): ``` POST /InitiateBasePackagePayment Body: { "callback_url": "https://example.com/profile/payment-callback" } Response: { "success": true, "message": "...", "order_id": 123, "transaction_id": 456, "amount": 56000000, "payment_gateway_url": "https://www.zarinpal.com/pg/StartPay/...", "authority": "A00000000000000000000000000123456" } ``` ``` POST /VerifyBasePackagePayment Body: { "order_id": 123, "transaction_id": 456, "authority": "A00000000000000000000000000123456", "status": "OK" } Response: { "success": true, "message": "پرداخت با موفقیت تایید شد", "order_id": 123, "transaction_id": 456, "ref_id": "789", "wallet_balance": 56000000, "discount_balance": 56000000 } ``` --- ## 📊 Flow Diagram ### Complete Payment Flow: ```mermaid sequenceDiagram participant User as کاربر participant FE as Frontend participant BFF as BFF participant CMS as CMS participant PYMS as PYMS participant Bank as درگاه بانک User->>FE: کلیک "پرداخت مستقیم" FE->>BFF: InitiateBasePackagePayment(CallbackUrl) BFF->>BFF: استخراج UserId از JWT BFF->>CMS: InitiateBasePackagePayment(UserId) CMS->>CMS: ثبت Transaction (Pending) CMS->>CMS: ثبت Order (Pending) CMS-->>BFF: OrderId, TransactionId, Amount BFF->>PYMS: PaymentRequest(Amount, Callback) PYMS-->>BFF: PaymentGWUrl, Authority BFF-->>FE: PaymentGWUrl, OrderId, TransactionId FE->>Bank: Redirect to PaymentGWUrl User->>Bank: پرداخت Bank-->>FE: Redirect to Callback?Authority=...&Status=OK FE->>BFF: VerifyBasePackagePayment(OrderId, TransactionId, Authority, Status) BFF->>PYMS: PaymentVerification(Authority) PYMS-->>BFF: PaymentStatus, RefId alt پرداخت موفق BFF->>CMS: VerifyBasePackagePayment(PaymentSuccess=true, RefId) CMS->>CMS: شارژ Balance (56M) CMS->>CMS: شارژ DiscountBalance (56M) CMS->>CMS: ثبت Transaction (Success) CMS->>CMS: ثبت WalletChangeLog CMS->>CMS: Update Order (Success) CMS->>CMS: Update User.PackagePurchaseMethod CMS-->>BFF: Success, WalletBalance, DiscountBalance BFF-->>FE: Success FE-->>User: نمایش پیام موفقیت + موجودی else پرداخت ناموفق BFF->>CMS: VerifyBasePackagePayment(PaymentSuccess=false) CMS->>CMS: Update Transaction (Reject) CMS->>CMS: Update Order (Reject) CMS-->>BFF: Failed BFF-->>FE: Failed FE-->>User: نمایش پیام خطا end ``` --- ## ⚠️ نکات مهم ### Security: 1. **UserId از JWT گرفته می‌شود** نه از Request - امنیت بالاتر 2. **Validation در هر لایه** انجام می‌شود 3. **Transaction Idempotency** - چک می‌شود که Order Pending قبلی وجود نداشته باشد ### Business Logic: 1. کاربر **فقط یک بار** می‌تواند پکیج پایه بخرد 2. **شارژ هم‌زمان** Balance و DiscountBalance انجام می‌شود 3. **PackagePurchaseMethod** بعد از پرداخت موفق به `DirectPurchase` تغییر می‌کند 4. برای فعالسازی لینک دعوت، باید **هم پکیج خریداری شود هم باشگاه فعال شود** ### Error Handling: 1. اگر CMS خطا برگرداند، به درگاه نمی‌رویم 2. اگر PYMS URL ندهد، Transaction در CMS باقی می‌ماند (Pending) 3. اگر Callback با Status=NOK بیاید، مستقیماً Reject می‌شود 4. اگر Verification ناموفق باشد، Transaction و Order به Reject تغییر می‌کند ### Project References: برای development، از Project Reference استفاده می‌شود: - BFF → CMS.Protobuf (Project Reference) - Frontend → BFF.Package.Protobuf (Project Reference) برای production، باید به NuGet Package تبدیل شوند. --- ## ✅ Checklist پیاده‌سازی ### CMS: - [x] InitiateBasePackagePaymentCommand - [x] InitiateBasePackagePaymentCommandValidator - [x] InitiateBasePackagePaymentCommandHandler - [x] VerifyBasePackagePaymentCommand - [x] VerifyBasePackagePaymentCommandValidator - [x] VerifyBasePackagePaymentCommandHandler - [x] Proto messages و RPCs - [x] PackageService implementation - [x] Mapster mappings ### BFF: - [x] InitiateBasePackagePaymentCommand - [x] InitiateBasePackagePaymentCommandValidator - [x] InitiateBasePackagePaymentCommandHandler - [x] VerifyBasePackagePaymentCommand - [x] VerifyBasePackagePaymentCommandValidator - [x] VerifyBasePackagePaymentCommandHandler - [x] Proto messages و RPCs - [x] PackageService implementation - [x] Mapster mappings - [x] CurrentUserService integration ### Frontend: - [x] Bottom Sheet UI برای انتخاب روش پرداخت - [x] DirectPayment method - [x] PaymentCallback page - [x] UserAuthInfo.UserId - [x] AuthService extract UserId - [x] Navigation to payment gateway - [x] Display payment result ### Testing: - [ ] Test پرداخت موفق - [ ] Test پرداخت ناموفق - [ ] Test لغو پرداخت توسط کاربر - [ ] Test خرید مجدد (باید خطا دهد) - [ ] Test شارژ کیف پول - [ ] Test فعالسازی لینک دعوت --- **تاریخ آخرین به‌روزرسانی:** 2024-12-16 **نگارنده:** Development Team