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

12 KiB
Raw Blame History

💰 سیستم مالی، پرداخت و درگاه‌ها

منابع ادغام‌شده: payment-gateway.md, payment-architecture-pyms.md, daya-loan-integration.md, manual-payment-system.md, discount-shop-business.md
آخرین بروزرسانی: اسفند ۱۴۰۴ (بروزرسانی: فیکس Toman/Rial + PackagePurchaseDialog + اعمال migrations بر staging)


۱. معماری کلی مالی

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 کیف‌پول

۲.۱ فلوی پرداخت

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.PriceamountInRials = (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)

۳.۱ معماری

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

// appsettings.json
"DayaLoan": {
    "UseMock": true,         // staging
    "BaseUrl": "https://api.dayaloan.ir",
    "ApiKey": "***",
    "AutoApproveInMock": true
}

۳.۳ مقادیر

آیتم مقدار
مبلغ وام (DayaLoanAmount) ۵۶,۰۰۰,۰۰۰ ریال
شارژ Balance ۵۶,۰۰۰,۰۰۰ ریال
شارژ DiscountBalance ۱۱۲,۰۰۰,۰۰۰ ریال (دو برابر — DayaLoanAmount × 2)
مجموع شارژ ۱۶۸,۰۰۰,۰۰۰ ریال
NetworkBalance شارژ نمی‌شود
بازپرداخت طبق شرایط دایا

۴. پرداخت دستی (کارت‌به‌کارت)

⚠️ وضعیت: طراحی‌شده — پیاده‌سازی نشده

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) تعیین‌کننده است.

۵.۲ فلوی خرید فروشگاه اعتباری

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

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) اعمال شد.