3c729304db
Corrections verified against actual CMS/BackOffice/FrontOffice source code: - ClubActivationFee: 25,200,000 (not 25,000,000) - Tree depth: no limit (15 is commission calculation depth only) - IPG wallet charge: Balance=56M + Discount=56M - DayaLoan wallet charge: Balance=56M + Discount=112M (2×) - Discount: per-product MaxDiscountPercent (not fixed 30%) - VAT: 10% (ShopVAT) vs 9% (discount store PlaceOrder) - Kavenegar template: 'Afrino' only (not verify-foursat) - SMS sender: 1000001110100 - DayaLoan job: every 20min (not 15min) - Commission job: Sunday 00:05 (not Saturday) - Network tree: on User entity (not separate NetworkNode table) - UserWallets entity (not UserWalletBalances) - OTP: 6 digits, 5 attempts, 2min TTL, 60s cooldown - Removed non-existent constants (ClubJoiningPercentage, ClubActivationThreshold) - Fixed Hangfire Chatika interval: every 5min - Removed InventorySync from recurring jobs list
8.0 KiB
8.0 KiB
💰 سیستم مالی، پرداخت و درگاهها
منابع ادغامشده:
payment-gateway.md,payment-architecture-pyms.md,daya-loan-integration.md,manual-payment-system.md,discount-shop-business.md
آخرین بروزرسانی: اسفند ۱۴۰۴
۱. معماری کلی مالی
┌──────────────────────────────────────────────────────────────────┐
│ FourSat Payment Architecture │
├──────────────┬──────────────┬──────────────┬─────────────────────┤
│ ZarinPal │ Daya Loan │ Manual Pay │ Discount Wallet │
│ (IPG) │ (API) │ (Card2Card) │ (Internal) │
├──────────────┴──────────────┴──────────────┴─────────────────────┤
│ PYMS (Payment Service) │
│ gRPC ←→ CMS ←→ FrontOffice/BackOffice │
├──────────────────────────────────────────────────────────────────┤
│ 3 Wallet System │
│ Balance (نقدی) │ NetworkBalance (شبکه) │ DiscountBalance │
└──────────────────────────────────────────────────────────────────┘
۲. درگاه ZarinPal (IPG)
۲.۱ فلوی پرداخت
کاربر → انتخاب محصول → درخواست پرداخت
│
▼
CMS → CreatePaymentRequest (gRPC to PYMS)
│
▼
PYMS → ZarinPal API → دریافت Authority
│
▼
Redirect کاربر → صفحه پرداخت ZarinPal
│
▼
بازگشت با Authority → CMS VerifyPayment
│
├─→ موفق: ثبت سفارش + شارژ کیفپول (در صورت نیاز)
└─→ ناموفق: نمایش پیام خطا
۲.۲ تنظیمات ZarinPal
| پارامتر | مقدار |
|---|---|
MerchantId |
از appsettings |
CallbackUrl |
/payment/callback |
Sandbox |
true (staging) / false (production) |
Currency |
IRR (ریال → تبدیل به تومان در UI) |
۳. سیستم وام دایا (DayaLoan)
۳.۱ معماری
Hangfire Recurring Job (هر ۲۰ دقیقه — cron: */20 * * * *)
│
▼
DayaLoanProcessorJob.Execute()
│
▼
بررسی LoanRequests با Status=Pending
│
▼
برای هر درخواست:
├─→ ارسال به DayaLoan API ([AutomaticRetry(Attempts=3)])
├─→ در صورت تأیید: Balance += 56M, DiscountBalance += 112M (2×)
├─→ ثبت Transaction + Log
└─→ در صورت رد: Status=Rejected + ارسال SMS
۳.۲ Mock Mode
// appsettings.json
"DayaLoan": {
"UseMock": true, // staging
"BaseUrl": "https://api.dayaloan.ir",
"ApiKey": "***",
"AutoApproveInMock": true
}
۳.۳ مقادیر
| آیتم | مقدار |
|---|---|
| مبلغ وام (DayaLoanAmount) | ۵۶,۰۰۰,۰۰۰ ریال |
| شارژ Balance | ۵۶,۰۰۰,۰۰۰ ریال |
| شارژ DiscountBalance | ۱۱۲,۰۰۰,۰۰۰ ریال (دو برابر — DayaLoanAmount × 2) |
| مجموع شارژ | ۱۶۸,۰۰۰,۰۰۰ ریال |
| NetworkBalance | شارژ نمیشود |
| بازپرداخت | طبق شرایط دایا |
۴. پرداخت دستی (کارتبهکارت)
⚠️ وضعیت: طراحیشده — پیادهسازی نشده
فلوی پیشنهادی:
کاربر → انتخاب "کارتبهکارت"
│
▼
نمایش شمارهکارت مقصد + مبلغ
│
▼
کاربر → واریز + آپلود تصویر رسید
│
▼
ادمین BackOffice → مشاهده لیست درخواستها
│
▼
تأیید/رد → شارژ خودکار کیفپول
موجودیتهای مورد نیاز:
ManualPaymentRequest(UserId, Amount, ReceiptImage, Status, AdminNote)ManualPaymentStatusenum: Pending, Approved, Rejected
۵. پرداخت ترکیبی فروشگاه تخفیفی (Hybrid Payment)
۵.۱ فرمول
قیمت محصول = 1,000,000 ریال
MaxDiscountPercent محصول = 40% (هر محصول درصد تخفیف مخصوص خود را دارد)
سهم تخفیف = MIN(1,000,000 × 40%, DiscountBalanceکاربر) = 400,000
پرداخت نقدی (IPG) = 1,000,000 - 400,000 = 600,000
─────────
مجموع = 1,000,000
ℹ️ تخفیف ثابت ۳۰% نیست — فیلد Product.MaxDiscountPercent (0-100) تعیینکننده است.
۵.۲ فلوی خرید فروشگاه تخفیفی
کاربر (عضو باشگاه) → مشاهده محصول
│
▼
قیمت تخفیفخورده نمایش داده میشود
│
▼
افزودن به سبد → بررسی DiscountBalance
│
├─→ DiscountBalance کافی:
│ سهم تخفیف از DiscountBalance کسر
│ باقیمانده → IPG (ZarinPal)
│
└─→ DiscountBalance ناکافی:
فقط به اندازه موجودی از تخفیف
باقیمانده بیشتر → IPG
۵.۳ دسترسی فروشگاه تخفیفی
| شرط | نتیجه |
|---|---|
IsClubMember = true |
دسترسی به Discount Store |
IsClubMember = false |
فقط Regular Store |
DiscountBalance > 0 |
میتواند از تخفیف استفاده کند |
DiscountBalance = 0 |
پرداخت ۱۰۰% نقدی |
۶. PYMS — سرویس پرداخت مرکزی
۶.۱ gRPC Services
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 | شارژ از وام دایا |
۷. مالیات و VAT
دو نرخ VAT در کد:
SystemConstants.ShopVAT = 0.1 (10%) — تنظیم سیستمی
VAT_RATE = 0.09 (9%) — در فروشگاه تخفیفی (DiscountShop PlaceOrder)
قیمت نمایشی = قیمت پایه × (1 + VAT)
در صورتحساب: قیمت پایه + مالیات جداگانه نمایش داده میشود
۸. خلاصه وضعیت پیادهسازی
| ماژول | وضعیت | یادداشت |
|---|---|---|
| ZarinPal IPG | ✅ کامل | Production ready |
| وام دایا | ✅ کامل | Mock mode فعال در staging |
| پرداخت ترکیبی | ✅ کامل | Discount + IPG |
| Pool هفتگی | ✅ کامل | SP + Hangfire |
| پرداخت دستی | ⬜ طراحی | نیاز به تصمیم مدیریت |
| Refund | ⬜ طراحی | فقط در PYMS تعریفشده |