Files
docs/business/BUSINESS-02-PAYMENT-FINANCE.md
T
masoodafar-web 39590d2cbe docs: Phase 10 — DataMigration + EF Staging + PackagePurchaseDialog + UI Fixes
Updated 8 docs:
- CHANGELOG: Phase 10a-d (DataMigration tool, EF staging migrations, PackagePurchaseDialog, 4 UI fixes)
- PAYMENT-FINANCE: Rial→Toman conversion chain documented, PackagePurchaseDialog status
- TECH-02: Added PackagePurchaseDialog to folder structure + status table
- TECH-04: DataMigration tool features (smart retry, FK handling, fallback tables), EF staging
- PACKAGE-TASKS: Phase 10 graph, NuGet v0.0.189, T4.1+T4.2 marked 
- BIZ-PACKAGE: v6→v7, commits updated, T4.1+T4.2 marked 
- INDEX: Updated last-update + R3 description
- ROADMAP: Progress bars updated, DONE section + NOW section refreshed
2026-02-27 20:56:00 +03:30

268 lines
12 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.
# 💰 سیستم مالی، پرداخت و درگاه‌ها
> **منابع ادغام‌شده:** `payment-gateway.md`, `payment-architecture-pyms.md`, `daya-loan-integration.md`, `manual-payment-system.md`, `discount-shop-business.md`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس Toman/Rial + PackagePurchaseDialog + اعمال migrations بر staging)
---
## ۱. معماری کلی مالی
```mermaid
flowchart TD
subgraph GATEWAYS["درگاه‌ها"]
ZP["ZarinPal\nIPG"]
DL["Daya Loan\nAPI"]
MP["Manual Pay\nCard2Card"]
DW["Discount Wallet\nInternal"]
end
ZP --> PYMS["PYMS — Payment Service\ngRPC ↔ CMS ↔ FrontOffice/BackOffice"]
DL --> PYMS
MP --> PYMS
DW --> PYMS
PYMS --> WALLETS
subgraph WALLETS["3 Wallet System"]
W1["💰 Balance\nکیف پول اصلی"]
W2["🌟 NetworkBalance\nپاداش تیمی"]
W3["🏷️ DiscountBalance\nکیف پول اعتباری"]
end
```
---
## ۲. درگاه ZarinPal (IPG)
> **✅ محل استفاده:** ZarinPal در چهار جا فعال است:
> 1. **فروشگاه اعتباری** — باقیمانده بعد از کسر DiscountBalance (اگر > 0)
> 2. **شارژ کیف‌پول اعتباری** — واریز مستقیم از پروفایل کاربر
> 3. **خرید پکیج** — پرداخت مستقیم با کارت بانکی (هر دو شاخه فعال)
> 4. **شارژ کیف‌پول جادویی** — واریز با ضریب ×2.5 (فقط در حالت Magic)
>
> ❌ **فروشگاه عادی (Regular Store) از ZarinPal استفاده نمی‌کند** — فقط کسر از Balance کیف‌پول
### ۲.۱ فلوی پرداخت
```mermaid
flowchart TD
A["کاربر → انتخاب محصول\nدرخواست پرداخت"] --> B["CMS → CreatePaymentRequest\ngRPC to PYMS"]
B --> C["PYMS → ZarinPal API\nدریافت Authority"]
C --> D["Redirect کاربر\nصفحه پرداخت ZarinPal"]
D --> E["بازگشت با Authority\nCMS VerifyPayment"]
E -->|موفق| F["✅ ثبت سفارش\n+ شارژ کیف‌پول"]
E -->|ناموفق| G["❌ نمایش پیام خطا"]
```
### ۲.۲ تنظیمات ZarinPal
| پارامتر | مقدار |
|----------|-------|
| `MerchantId` | `4225d555-5fa9-4df0-9b61-1ce152cbbba8` |
| `CallbackUrl` | `/payment/callback` |
| `Sandbox` | `true` (staging) / `false` (production) |
| `Currency` | IRR (ریال → تبدیل به تومان در UI) |
> **✅ توضیح تبدیل Rial → Toman:**
> - **DB:** `Package.Price` به **ریال** ذخیره می‌شود (`/// قیمت پکیج (ریال)`)
> - **CMS → ZarinPal:** مستقیم ریال ارسال می‌شود (`Amount = package.Price` → `amountInRials = (long)request.Amount`)
> - **FrontOffice UI:** `PackageDto.FormattedPrice` = `Price / 10` + «تومان» (فیکس شد در `3c1a8ff`)
> - **باگ قبلی:** FO مقدار خام ریال را با برچسب «تومان» نمایش می‌داد (مثلاً ۵۶۰،۰۰۰،۰۰۰ تومان بجای ۵۶،۰۰۰،۰۰۰ تومان)
> **تنظیمات محیطی:**
> - `appsettings.json` + `appsettings.Staging.json`: `UseSandbox: true` (تست)
> - `appsettings.Production.json`: `UseSandbox: false` (واقعی)
> - Production URL: `cms.kbs1.ir` | FrontOffice GwUrl: `cms.kbs2.ir`
---
## ۳. سیستم وام دایا (DayaLoan)
### ۳.۱ معماری
```mermaid
flowchart TD
A["Hangfire Recurring Job\nهر ۲۰ دقیقه — */20 * * * *"] --> B["DayaLoanProcessorJob.Execute"]
B --> C["بررسی LoanRequests\nStatus = Pending"]
C --> D["برای هر درخواست:"]
D --> E["ارسال به DayaLoan API\nAutomaticRetry Attempts=3"]
E -->|تأیید| F["✅ Balance += 56M\nDiscountBalance += 112M\n+ ثبت Transaction + Log"]
E -->|رد| G["❌ Status = Rejected\n+ ارسال SMS"]
```
### ۳.۲ Mock Mode
```csharp
// appsettings.json
"DayaLoan": {
"UseMock": true, // staging
"BaseUrl": "https://api.dayaloan.ir",
"ApiKey": "***",
"AutoApproveInMock": true
}
```
### ۳.۳ مقادیر
| آیتم | مقدار |
|------|-------|
| مبلغ وام (DayaLoanAmount) | ۵۶,۰۰۰,۰۰۰ ریال |
| شارژ Balance | ۵۶,۰۰۰,۰۰۰ ریال |
| شارژ DiscountBalance | ۱۱۲,۰۰۰,۰۰۰ ریال (دو برابر — DayaLoanAmount × 2) |
| مجموع شارژ | ۱۶۸,۰۰۰,۰۰۰ ریال |
| NetworkBalance | شارژ نمی‌شود |
| بازپرداخت | طبق شرایط دایا |
---
## ۴. پرداخت دستی (کارت‌به‌کارت)
> ⚠️ **وضعیت: طراحی‌شده — پیاده‌سازی نشده**
```mermaid
flowchart TD
A["کاربر → انتخاب کارت‌به‌کارت"] --> B["نمایش شماره‌کارت مقصد\n+ مبلغ"]
B --> C["کاربر → واریز\n+ آپلود تصویر رسید"]
C --> D["ادمین BackOffice\nمشاهده لیست درخواست‌ها"]
D --> E{"تأیید / رد؟"}
E -->|تأیید| F["✅ شارژ خودکار کیف‌پول"]
E -->|رد| G["❌ اطلاع‌رسانی به کاربر"]
```
**موجودیت‌های مورد نیاز:**
- `ManualPaymentRequest` (UserId, Amount, ReceiptImage, Status, AdminNote)
- `ManualPaymentStatus` enum: Pending, Approved, Rejected
---
## ۵. پرداخت ترکیبی فروشگاه اعتباری (Hybrid Payment)
### ۵.۱ فرمول
```
قیمت محصول = 1,000,000 ریال
MaxDiscountPercent محصول = 40% (هر محصول درصد تخفیف مخصوص خود را دارد)
سهم تخفیف = قیمت × MaxDiscountPercent% = 400,000
⚠️ اگر DiscountBalance < سهم تخفیف → خطا: «موجودی کیف پول اعتباری کافی نیست»
(دیگر MIN استفاده نمی‌شود — کاربر باید موجودی کافی داشته باشد)
باقیمانده → ZarinPal IPG = 1,000,000 - 400,000 = 600,000
─────────
مجموع = 1,000,000
ℹ️ تخفیف ثابت ۳۰% نیست — فیلد Product.MaxDiscountPercent (0-100) تعیین‌کننده است.
```
### ۵.۲ فلوی خرید فروشگاه اعتباری
```mermaid
flowchart TD
A["کاربر عضو باشگاه\nمشاهده محصول"] --> B["قیمت تخفیف‌خورده نمایش داده می‌شود"]
B --> C["افزودن به سبد\nمحاسبه MaxDiscount% هر محصول"]
C --> D["سهم تخفیف = قیمت × MaxDiscount%"]
D --> V{"DiscountBalance >= سهم تخفیف?"}
V -->|خیر| X["❌ خطا: موجودی کیف پول اعتباری کافی نیست"]
V -->|بله| E["باقیمانده = مجموع - سهم تخفیف"]
E --> F{"باقیمانده > 0?"}
F -->|بله| G["کسر DiscountBalance\n+ Redirect → ZarinPal IPG\nباقیمانده + 10% VAT"]
F -->|خیر| H["فقط کسر از DiscountBalance\nبدون درگاه → ثبت مستقیم"]
G --> LOG["ثبت UserWalletChangeLog"]
H --> LOG
```
### ۵.۳ دسترسی فروشگاه اعتباری
| شرط | نتیجه |
|------|--------|
| `IsClubMember = true` | دسترسی به Discount Store |
| `IsClubMember = false` | فقط Regular Store |
| `DiscountBalance >= سهم تخفیف` | خرید مجاز |
| `DiscountBalance < سهم تخفیف` | ❌ خطا: موجودی کیف پول اعتباری کافی نیست |
### ۵.۴ UserWalletChangeLog (اسفند ۱۴۰۴ — فیکس)
> **باگ:** هنگام خرید از فروشگاه اعتباری، `DiscountBalance` در دیتابیس کم می‌شد ولی هیچ
> `UserWalletChangeLog` ثبت نمی‌شد → کاربر در تاریخچه کیف‌پول چیزی نمی‌دید.
فیکس در ۳ هندلر:
| هندلر | سناریو | فیکس |
|--------|---------|------|
| `PlaceOrderCommandHandler` | پرداخت کامل با DiscountBalance (بدون درگاه) | ✅ ثبت log با `ChangeDiscountValue = -amount` |
| `CompleteOrderPaymentCommandHandler` | پرداخت ترکیبی (درگاه + DiscountBalance) | ✅ ثبت log بعد از verify موفق درگاه |
| `VerifyDiscountWalletChargeCommandHandler` | شارژ کیف‌پول اعتباری | ✅ ثبت log با `ChangeDiscountValue = +amount` |
---
## ۶. PYMS — سرویس پرداخت مرکزی
### ۶.۱ gRPC Services
```protobuf
service PaymentService {
rpc CreatePayment (CreatePaymentRequest) returns (CreatePaymentResponse);
rpc VerifyPayment (VerifyPaymentRequest) returns (VerifyPaymentResponse);
rpc GetPaymentStatus (GetPaymentStatusRequest) returns (PaymentStatusResponse);
rpc RefundPayment (RefundPaymentRequest) returns (RefundPaymentResponse);
}
```
### ۶.۲ Transaction Types
| نوع | کد | توضیح |
|-----|-----|--------|
| PackagePurchase | 1 | خرید پکیج طلایی |
| StorePurchase | 2 | خرید از فروشگاه |
| DiscountStorePurchase | 3 | خرید از فروشگاه اعتباری |
| CommissionPayout | 4 | واریز کمیسیون هفتگی |
| WalletCharge | 5 | شارژ مستقیم کیف‌پول |
| ActivationFee | 6 | هزینه فعالسازی |
| DayaLoanCharge | 7 | شارژ از وام دایا |
| MagicWalletDeposit | 14 | واریز به کیف‌پول جادویی |
| MagicWalletBonus | 15 | بونوس ضریب ×2.5 کیف‌پول جادویی |
---
## ۷. مالیات و VAT
```
هر دو فروشگاه از نرخ 10% استفاده می‌کنند:
Regular Store → const vatRate = 0.10m (hardcoded در SubmitShopBuyOrderCommandHandler)
Discount Store → VatCalculator.VAT_RATE = 0.10m
SystemConstants.ShopVAT = 0.1 (10%)
قیمت نمایشی = قیمت پایه × (1 + 0.10)
در صورتحساب: قیمت پایه + مالیات جداگانه نمایش داده می‌شود
```
---
## ۸. خلاصه وضعیت پیاده‌سازی
| ماژول | وضعیت | یادداشت |
|-------|--------|---------|
| ZarinPal IPG | ✅ کامل | **Production فعال** — MerchantId: `4225d555...` |
| وام دایا | ✅ کامل | Mock mode فعال در staging |
| پرداخت ترکیبی | ✅ کامل | Discount + IPG |
| Pool هفتگی | ✅ کامل | SP + Hangfire |
| WalletChangeLog | ✅ فیکس شده | لاگ تغییرات کیف‌پول در ۳ هندلر اضافه شد |
| Validation کیف‌پول اعتباری | ✅ فیکس شده | ارور اگر موجودی کافی نباشد |
| Toman/Rial تبدیل نمایش | ✅ فیکس شده | `Price / 10` در FO — درگاه ریال می‌گیرد، UI تومان نمایش می‌دهد |
| PackagePurchaseDialog | ✅ کامل | دیالوگ داینامیک کاشی‌ای با انتخاب روش پرداخت |
| پرداخت دستی | ⬜ طراحی | نیاز به تصمیم مدیریت |
| Refund | ⬜ طراحی | فقط در PYMS تعریف‌شده |
| کیف‌پول جادویی (Magic) | ✅ کامل | فاز 1-6 پیاده‌سازی شده — Production فعال |
### ۸.۱ نام‌گذاری استاندارد کیف‌پول‌ها (اسفند ۱۴۰۴)
| فیلد دیتابیس | نام قدیم (UI) | نام جدید (UI) |
|-------------|--------------|---------------|
| `Balance` | عادی / اعتباری / نقدی | **کیف پول اصلی** |
| `DiscountBalance` | تخفیفی / تخفیف | **کیف پول اعتباری** |
| `NetworkBalance` | شبکه / طلایی / پورسانت | **پاداش تیمی** |
> تغییرات UI در ۱۲ فایل (FrontOffice: 5, BackOffice: 7) اعمال شد.