docs: فاز ۱۱ — فیکس‌های پرداخت ZarinPal + تصحیح تومان/ریال + امنیت Callback URL

- CHANGELOG: Phase 11 (11a-11f) — ZarinPal verify fix, تومان/ریال مدل, صفحه موفقیت, حذف ×۱۰ دوبار, callback URL امنیت
- BUSINESS-02: تصحیح مدل ارزی (DB=تومان نه ریال), ZarinPal verify fix, جدول callback URL امنیت
- TECH-01: اضافه CmsBaseUrl/FrontOfficeBaseUrl به appsettings, توضیح امنیت Open Redirect
- TECH-02: اضافه PaymentCallback.razor, وضعیت‌های جدید
- ROADMAP: بروزرسانی Payment 97→99%, اضافه فاز ۱۱ به DONE list
This commit is contained in:
masoodafar-web
2026-02-27 22:33:35 +03:30
parent 39590d2cbe
commit e3850f9dd8
5 changed files with 124 additions and 26 deletions
+28 -11
View File
@@ -1,7 +1,7 @@
# 💰 سیستم مالی، پرداخت و درگاه‌ها
> **منابع ادغام‌شده:** `payment-gateway.md`, `payment-architecture-pyms.md`, `daya-loan-integration.md`, `manual-payment-system.md`, `discount-shop-business.md`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس Toman/Rial + PackagePurchaseDialog + اعمال migrations بر staging)
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: تصحیح مدل تومان/ریال + فیکس ZarinPal Verify + امنیت Callback URL)
---
@@ -47,27 +47,41 @@ flowchart TD
```mermaid
flowchart TD
A["کاربر → انتخاب محصول\nدرخواست پرداخت"] --> B["CMS → CreatePaymentRequest\ngRPC to PYMS"]
B --> C["PYMS → ZarinPal API\nدریافت Authority"]
B --> C["PYMS → ZarinPal API\nمبلغ ×۱۰ (تومان→ریال)\nدریافت Authority"]
C --> D["Redirect کاربر\nصفحه پرداخت ZarinPal"]
D --> E["بازگشت با Authority\nCMS VerifyPayment"]
D --> E["بازگشت با Authority\nCMS VerifyPayment (مبلغ ×۱۰)"]
E -->|موفق| F["✅ ثبت سفارش\n+ شارژ کیف‌پول"]
E -->|ناموفق| G["❌ نمایش پیام خطا"]
```
> **✅ فیکس ZarinPal Verify (اسفند ۱۴۰۴ — `721661a`):**
> - **باگ:** `VerifyPaymentAsync(authority)` با ۲ آرگومان → amount=0 → ZarinPal Code=-1
> - **فیکس:** lookup `PaymentTransaction.Amount` از DB + استفاده از overload ۳ آرگومانه `VerifyPaymentAsync(authority, orderId, amount)`
> - `IPaymentGatewayService` — default impl ۳ آرگومانه اضافه شد
> - ۷ فایل تغییر: PackageService, TransactionsService, VerifyDiscountWalletChargeCommandHandler, VerifyPackagePurchaseCommandHandler, IPaymentGatewayService, MockPaymentGatewayService, DayaPaymentService
### ۲.۲ تنظیمات ZarinPal
| پارامتر | مقدار |
|----------|-------|
| `MerchantId` | `4225d555-5fa9-4df0-9b61-1ce152cbbba8` |
| `CallbackUrl` | `/payment/callback` |
| `CallbackUrl` | از `appsettings.json` خوانده می‌شود (نه از ورودی کاربر) |
| `Sandbox` | `true` (staging) / `false` (production) |
| `Currency` | IRR (ریال → تبدیل به تومان در UI) |
| `Currency` | DB: تومان — ZarinPal: ریال (×۱۰ هنگام ارسال) |
> **✅ توضیح تبدیل Rial → Toman:**
> - **DB:** `Package.Price` به **ریال** ذخیره می‌شود (`/// قیمت پکیج (ریال)`)
> - **CMS → ZarinPal:** مستقیم ریال ارسال می‌شود (`Amount = package.Price` → `amountInRials = (long)request.Amount`)
> - **FrontOffice UI:** `PackageDto.FormattedPrice` = `Price / 10` + «تومان» (فیکس شد در `3c1a8ff`)
> - **باگ قبلی:** FO مقدار خام ریال را با برچسب «تومان» نمایش می‌داد (مثلاً ۵۶۰،۰۰۰،۰۰۰ تومان بجای ۵۶،۰۰۰،۰۰۰ تومان)
> **✅ مدل ارزی (تصحیح اسفند ۱۴۰۴):**
> - **DB:** `Package.Price` و همه مبالغ مالی به **تومان** ذخیره می‌شوند
> - **CMS → ZarinPal:** `ZarinPalPaymentService` مبلغ را ×۱۰ می‌کند (`amountInRials = amount * 10`)
> - **FrontOffice UI:** مبالغ مستقیم به تومان نمایش داده می‌شوند (بدون تبدیل)
> - **FrontOffice → CMS:** مبالغ به تومان ارسال می‌شوند (FO هیچ تبدیلی انجام نمی‌دهد)
> - **باگ قبلی ۱:** FO مبلغ تومان را ×۱۰ تبدیل می‌کرد + CMS/ZarinPal دوباره ×۱۰ → مبلغ ۱۰۰ برابر (فیکس: `2f9ef15`)
> - **باگ قبلی ۲:** `FormattedPrice = Price / 10` اشتباه بود — Price از قبل تومان است (فیکس: `3c1a8ff` اصلاح شد)
> **✅ امنیت Callback URL (اسفند ۱۴۰۴):**
> - هیچ callback URL از ورودی کاربر خوانده نمی‌شود — همه از `appsettings.json` خوانده می‌شوند
> - `PackageService` و `TransactionsService`: از `FrontOfficeBaseUrl` config
> - `MagicWallet` و `DiscountWallet`: از `CmsBaseUrl` config
> - جلوگیری از حمله Open Redirect
> **تنظیمات محیطی:**
> - `appsettings.json` + `appsettings.Staging.json`: `UseSandbox: true` (تست)
@@ -245,12 +259,15 @@ service PaymentService {
| ماژول | وضعیت | یادداشت |
|-------|--------|---------|
| ZarinPal IPG | ✅ کامل | **Production فعال** — MerchantId: `4225d555...` |
| ZarinPal Verify | ✅ فیکس شده | رفع amount=0 با overload ۳ آرگومانه (`721661a`) |
| Callback URL امنیت | ✅ فیکس شده | همه از config خوانده می‌شوند — جلوگیری از Open Redirect |
| وام دایا | ✅ کامل | Mock mode فعال در staging |
| پرداخت ترکیبی | ✅ کامل | Discount + IPG |
| Pool هفتگی | ✅ کامل | SP + Hangfire |
| WalletChangeLog | ✅ فیکس شده | لاگ تغییرات کیف‌پول در ۳ هندلر اضافه شد |
| Validation کیف‌پول اعتباری | ✅ فیکس شده | ارور اگر موجودی کافی نباشد |
| Toman/Rial تبدیل نمایش | ✅ فیکس شده | `Price / 10` در FO — درگاه ریال می‌گیرد، UI تومان نمایش می‌دهد |
| Toman/Rial مدل | ✅ تصحیح شده | DB=تومان، فقط ZarinPal ریال (×۱۰) — FO بدون تبدیل |
| صفحه موفقیت پرداخت | ✅ بهبود | TransactionId + موجودی واقعی + دکمه بازگشت |
| PackagePurchaseDialog | ✅ کامل | دیالوگ داینامیک کاشی‌ای با انتخاب روش پرداخت |
| پرداخت دستی | ⬜ طراحی | نیاز به تصمیم مدیریت |
| Refund | ⬜ طراحی | فقط در PYMS تعریف‌شده |
+77 -8
View File
@@ -1,7 +1,7 @@
# 📜 تاریخچه کارهای انجام‌شده
> **همه فعالیت‌های پروژه به صورت بولت با توضیح یک‌خطی و درصد تکمیل**
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فاز ۱۰DataMigration + EF Staging + PackagePurchaseDialog + UI Fixes | NuGet v0.0.189)
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فاز ۱۱فیکس‌های پرداخت ZarinPal + امنیت Callback URL + اصلاح تومان/ریال)
---
@@ -10,12 +10,12 @@
| حوزه | تعداد آیتم | تکمیل‌شده | درصد کل |
|------|-----------|----------|---------|
| **BackOffice** | 67 | 67 | **100%** |
| **FrontOffice** | 54 | 54 | **100%** |
| **CMS Core** | 82 | 82 | **100%** |
| **FrontOffice** | 60 | 60 | **100%** |
| **CMS Core** | 89 | 89 | **100%** |
| **Package-Based System** | 52 | 52 | **100%** |
| **Deployment** | 22 | 21 | **95%** |
| **Migration** | 21 | 21 | **100%** |
| **مجموع** | **298** | **297** | **99%** |
| **مجموع** | **311** | **310** | **99.5%** |
---
@@ -106,6 +106,12 @@
- ✅ دکمه پرداخت شارژ کیف‌پول جادویی — MagicWallet.razor فعال شد (حذف «بزودی»)
- ✅ دکمه‌های پرداخت مستقیم خرید پکیج — Index.razor هر دو شاخه فعال شدند (حذف «بزودی»)
### فیکس‌های پرداخت و UX (اسفند ۱۴۰۴ — Phase 11)
-**صفحه موفقیت پرداخت**`TransactionId` بجای `RefId` + موجودی واقعی + `Href="/profile"` (FO:`5ded91a`)
-**حذف دوبار ×۱۰** — FO مستقیم تومان ارسال می‌کند، CMS/ZarinPal ×۱۰ می‌کند (FO:`2f9ef15`)
-**حذف CallbackUrl از درخواست**`Index.razor.cs` و `Checkout.razor.cs` دیگر URL ارسال نمی‌کنند (FO:`2b1dc47`)
- ✅ ۳ کامیت، ۷ فایل تغییر
### Phase 8a+8c: Checkout + Package Pages (اسفند ۱۴۰۴)
- ✅ Checkout wire-up — مهاجرت به `CustomerPurchasePackageAsync` (حذف dead code قدیمی)
- ✅ PackageDetail: `GetPackageAsync``GetCustomerPackageDetailsAsync` — features/specs از API (نه hardcoded)
@@ -123,12 +129,66 @@
- ✅ کامیت: `a3681a8` (FO)
### Phase 10b: ۴ فیکس UI پکیج (اسفند ۱۴۰۴)
-فیکس Toman/Rial قیمت از سرور به ریال می‌رسد، `FormattedPrice` حالا `Price / 10` می‌کند برای نمایش صحیح تومان
-تغییر لیبل «ضریب تخفیف» → «ضریب اعتبار» در دیالوگ خرید + صفحه لیست پکیج‌ها
- ✅ دکمه بازگشت در مرحله انتخاب روش پرداخت `MudIconButton ArrowForward` + `BackToList()` (قبلاً وجود داشت، تأیید شد)
-رندر HTML در توضیحات پکیج — `@((MarkupString)pkg.Description)` بجای متن ساده
-**Toman/Rial**: قیمت از سرور به ریال `FormattedPrice` حالا `Price / 10` برای نمایش صحیح تومان
-**لیبل**: «ضریب تخفیف» → «ضریب اعتبار» (دیالوگ + صفحه لیست پکیج‌ها)
-**دکمه بازگشت**: وجود داشت (`ArrowForward` + `BackToList`) — تأیید عملکرد
-**HTML Description**: `@((MarkupString)pkg.Description)` بجای متن ساده
- ✅ کامیت: `3c1a8ff` (FO)
### Phase 11: فیکس‌های پرداخت + تومان/ریال + امنیت Callback URL (اسفند ۱۴۰۴)
#### 11a: اصلاح مدل تومان/ریال (CMS+FO)
> **تصحیح مهم:** دیتابیس به **تومان** ذخیره می‌کند نه ریال. فقط درگاه ZarinPal ریال نیاز دارد (×۱۰).
-`ZarinPalPaymentService.InitiatePaymentAsync` — مبلغ ×۱۰ تبدیل به ریال فقط هنگام ارسال به ZarinPal
-`ZarinPalPaymentService.VerifyPaymentWithAmountAsync` — مبلغ ×۱۰ هنگام verify
- ✅ FrontOffice نمایش مستقیم مبلغ تومان (بدون `/10`) — فیکس `MagicWallet.razor`, `ChargeDiscountWallet.razor.cs`
- ✅ حذف `Price / 10` اضافی در `ClubMembershipContractDialog.razor`
#### 11b: فیکس ZarinPal Verify — رفع خطای Code=-1 (CMS:`721661a`)
> **باگ:** `VerifyPaymentAsync` با ۲ آرگومان مبلغ صفر (0) ارسال می‌کرد → ZarinPal Code=-1 برمی‌گرداند
-`PackageService` — lookup `PaymentTransaction.Amount` + استفاده از overload ۳ آرگومانه
-`TransactionsService` — همان فیکس
-`VerifyDiscountWalletChargeCommandHandler` — مبلغ از `PaymentTransaction` + رفع کپی‌پیست باگ
-`VerifyPackagePurchaseCommandHandler` — مبلغ از `PaymentTransaction`
-`IPaymentGatewayService` — default impl ۳ آرگومانه با `NotImplementedException`
-`MockPaymentGatewayService` + `DayaPaymentService` — اضافه overload ۳ آرگومانه
-۷ فایل تغییر
#### 11c: بهبود صفحه موفقیت پرداخت (FO:`5ded91a`)
-`PaymentCallback.razor` — نمایش `TransactionId` بجای `RefId` برای کد رهگیری
- ✅ نمایش موجودی واقعی کیف‌پول از `WalletService.GetBalancesAsync()` (نه مقدار ثابت)
- ✅ دکمه بازگشت: `Href="/profile"` بجای `history.back()` (جلوگیری از حلقه بازگشت به درگاه)
#### 11d: حذف دوبار ×۱۰ شارژ کیف‌پول (FO:`2f9ef15`)
> **باگ:** FO مبلغ تومان را ×۱۰ تبدیل به ریال می‌کرد، سپس CMS/ZarinPal دوباره ×۱۰ → مبلغ ۱۰۰ برابر
-`MagicWallet.razor.cs` — حذف تبدیل ×۱۰ (ارسال مستقیم تومان)
-`MagicWallet.razor` — فیکس Max و فیلتر preset مبالغ
-`ChargeDiscountWallet.razor.cs` — حذف تبدیل ×۱۰
-`ClubMembershipContractDialog.razor` — حذف `Price/10` اضافی
- ✅ ۴ فایل تغییر
#### 11e: فیکس مسیر Callback کیف‌پول (CMS:`ed2b20a`)
-`PaymentCallbackController` — مسیر redirect از `/magic-wallet` به `/profile/magic-wallet`
- ✅ ایجاد `appsettings.Development.json` — URL‌های محلی (`localhost:32846` و `localhost:5268`)
- ✅ تصحیح کامنت‌های proto: «ریال» → «تومان»
#### 11f: امنیت Callback URL — حذف از ورودی کاربر (CMS:`0107308`, FO:`2b1dc47`)
> **اصلاح امنیتی:** هیچ callback URL نباید از ورودی کاربر بیاید — همه از `appsettings.json` خوانده شوند
-`PackageService` — خواندن `FrontOfficeBaseUrl` از `IConfiguration` بجای `request.CallbackUrl`
-`TransactionsService` — همان فیکس، خواندن از config
- ✅ تأیید: `MagicWallet` و `DiscountWallet` از قبل از `CmsBaseUrl` config می‌خوانند ✅
- ✅ تأیید: `DiscountShop PlaceOrder` از قبل از `CmsBaseUrl` config می‌خواند ✅
- ✅ FO: حذف `CallbackUrl` از `Index.razor.cs` و `Checkout.razor.cs`
- ✅ جدول Callback URL‌ها:
| فلو | Callback URL | منبع |
|-----|-------------|------|
| خرید پکیج | `FrontOfficeBaseUrl/profile/payment-callback?orderId=X` | config |
| کیف‌پول جادویی | `CmsBaseUrl/api/wallet/verify-magic-charge` | config |
| کیف‌پول اعتباری | `CmsBaseUrl/api/wallet/verify-discount-charge` | config |
| فروشگاه اعتباری | `CmsBaseUrl/api/payment/discount-order/callback?orderId=X` | config |
| تراکنش عمومی | `FrontOfficeBaseUrl/profile/payment-callback` | config |
### محتوا و ناوبری
- ✅ بلاگ — لیست + جزئیات + pagination
- ✅ صفحات سایت — About, Contact, FAQ, Terms, Privacy, Licenses
@@ -191,6 +251,14 @@
- ✅ اغنای پاسخ UserWalletService — join با جدول Users برای نمایش نام کاربر
- ✅ ارتقای Proto NuGet به نسخه 0.0.183
### فیکس‌های پرداخت و امنیت (اسفند ۱۴۰۴ — Phase 11)
-**ZarinPal Verify fix** — رفع باگ amount=0 در VerifyPaymentAsync (Code=-1) — ۳ آرگومانه overload
-**تصحیح مدل تومان/ریال** — DB به تومان ذخیره می‌کند، فقط ZarinPal ریال (×۱۰) نیاز دارد
-**Callback URL از config**`PackageService` و `TransactionsService` از `FrontOfficeBaseUrl` config می‌خوانند (نه از ورودی)
-**فیکس مسیر redirect**`/magic-wallet``/profile/magic-wallet` در PaymentCallbackController
-**appsettings.Development.json** — URL‌های محلی برای توسعه (CmsBaseUrl + FrontOfficeBaseUrl)
- ✅ کامیت‌ها: `721661a``ed2b20a``0107308`
### محتوا
- ✅ Blog CRUD — با pagination
- ✅ SitePage Settings — JSON typed
@@ -431,3 +499,4 @@
| اسفند ۱۴۰۴ (هفته ۵) | 📦 فاز 8e: گزارش‌های پورسانت per-package (Proto + CMS + BO + FO) | 98% |
| اسفند ۱۴۰۴ (هفته ۶) | 📦 فاز ۹: Q24-Q30 (آستانه + SP Worker + History Tables + UI Guidance + Rename + Interceptor + Migration) | 99% |
| اسفند ۱۴۰۴ (هفته ۷) | 📦 فاز ۱۰: DataMigration Tool + EF Staging + PackagePurchaseDialog + UI Fixes (Toman/Rial + لیبل + HTML) | 99.5% |
| اسفند ۱۴۰۴ (هفته ۸) | 💳 فاز ۱۱: فیکس ZarinPal Verify + اصلاح تومان/ریال + صفحه موفقیت + حذف دوبار ×۱۰ + امنیت Callback URL | 99.5% |
+3 -2
View File
@@ -1,7 +1,7 @@
# 🗺️ نقشه راه، ریسک‌ها و کارهای باقیمانده
> **Roadmap + Risk Register + Dependencies + Priorities**
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فاز ۱۰DataMigration + EF Staging + PackagePurchaseDialog + UI Fixes)
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فاز ۱۱فیکس‌های پرداخت ZarinPal + امنیت Callback URL + تومان/ریال)
---
@@ -13,7 +13,7 @@
Core Platform ████████████████████████████████████████████████ 98%
Club System ████████████████████████████████████████████████ 98%
E-Commerce ████████████████████████████████████████████████ 98%
Payment ████████████████████████████████████████████████ 97%
Payment ████████████████████████████████████████████████ 99%
Magic Wallet ████████████████████████████████████████████████ 100%
Package-Based ███████████████████████████████████████████████░░ 97%
UI/UX ██████████████████████████████████████████████░░░ 95%
@@ -211,6 +211,7 @@ DONE (اسفند ۱۴۰۴):
→ 📦 تحول پکیج‌بیس فاز 0-6 ✅ (Domain → Migration → Business → Package → CRUD → Commission per-pkg → Deprecation)
→ 📦 فاز ۹: Q24-Q30 + History + Rename + Interceptor ✅
→ 📦 فاز ۱۰: DataMigration Tool + EF Staging + PackagePurchaseDialog + UI Fixes ✅
→ 💳 فاز ۱۱: فیکس ZarinPal Verify (amount=0) + تصحیح مدل تومان/ریال + صفحه موفقیت پرداخت + حذف دوبار ×۱۰ + امنیت Callback URL ✅
NOW (این ماه):
→ تست کامل پروداکشن
+11 -3
View File
@@ -1,7 +1,7 @@
# ⚙️ معماری CMS و زیرساخت فنی
> **منابع ادغام‌شده:** `CMS-README.md`, `ICURRENTUSERSERVICE-IMPLEMENTATION.md`, `FILE-MANAGEMENT-ARCHITECTURE.md`, `FRONTOFFICE-CMS-API-COMPATIBILITY.md`, `BFF-REMOVAL-PLAN.md`, `system-constants.md`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: History Interceptor + SP Worker + Rename UserWalletHistory)
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس ZarinPal Verify + Callback URL امنیت + appsettings.Development.json)
---
@@ -305,7 +305,15 @@ FrontOffice Service Layer:
"WorkerCount": 4
},
"Kavenegar": { "ApiKey": "***" },
"ZarinPal": { "MerchantId": "***" },
"DayaLoan": { "UseMock": true }
"ZarinPal": { "MerchantId": "***", "UseSandbox": true },
"DayaLoan": { "UseMock": true },
"CmsBaseUrl": "https://cms.se.kbs1.ir",
"FrontOfficeBaseUrl": "http://localhost:5268"
}
```
> **⚠️ نکات مهم appsettings:**
> - `CmsBaseUrl` — برای callback URL‌های درگاه (شارژ کیف‌پول جادویی/اعتباری)
> - `FrontOfficeBaseUrl` — برای redirect بعد پرداخت (خرید پکیج/تراکنش عمومی)
> - `appsettings.Development.json` — URL‌های localhost برای توسعه محلی
> - همه callback URL‌ها از config خوانده می‌شوند — هیچ URL از ورودی کاربر نمی‌آید (امنیت Open Redirect)
+5 -2
View File
@@ -1,7 +1,7 @@
# 🖥️ BackOffice و FrontOffice — معماری UI
> **منابع ادغام‌شده:** `BACKOFFICE-ARCHITECTURE.md`, `BACKOFFICE-STORE-UNIFICATION.md`, `UI-MODERNIZATION-PLAN.md`, `UI-UNIFICATION-PLAN.md`, `PHASE-1-COMPLETE.md`, `PHASE-3-COMPLETE.md`, `PRODUCT-IMAGES-SQUARE.md`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: PackagePurchaseDialog + UI Fixes + Toman/Rial)
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس‌های پرداخت Phase 11 + صفحه موفقیت + تومان/ریال + امنیت Callback)
---
@@ -119,7 +119,8 @@ FrontOffice/src/FrontOffice/
│ │ └── Contract.razor ← امضای قرارداد
│ ├── Profile/
│ │ ├── Index.razor ← داشبورد پروفایل + تایل Magic
│ │ ── MagicWallet.razor ← 🪄 کیف‌پول جادویی (NEW)
│ │ ── MagicWallet.razor ← 🪄 کیف‌پول جادویی
│ │ └── PaymentCallback.razor ← 💳 صفحه نتیجه پرداخت (TransactionId + موجودی واقعی)
│ ├── Blog/
│ ├── Auth/
│ │ ├── Login.razor
@@ -266,6 +267,8 @@ if (user.Identity?.IsAuthenticated == true) {
| فعال‌سازی دکمه‌های درگاه | ✅ | 100% |
| PackagePurchaseDialog | ✅ | 100% |
| Toman/Rial فیکس نمایش قیمت | ✅ | 100% |
| صفحه نتیجه پرداخت (PaymentCallback) | ✅ | 100% |
| امنیت Callback URL | ✅ | 100% |
| Mobile Responsive (Phase 7) | ⬜ | 0% |
| Dark Mode | ⬜ | 0% |
| PWA | ⬜ | 0% |