# 🪄 پلن پیاده‌سازی کیف‌پول جادویی > **مرجع:** [MAGIC-WALLET-SPEC](./MAGIC-WALLET-SPEC.md) > **تخمین کل:** ~۷ روز کاری > **وابستگی مستقل:** تکمیل ChargeDiscountWallet (ربطی به جادویی ندارد) --- ## فازبندی ### فاز ۱ — مدل داده و Migration (روز ۱) **هدف:** زیرساخت دیتابیس و enum‌ها ``` فایل‌های تغییری: ├── UserWallet.cs → + WalletMode, MagicTotalDeposited, MagicTotalCredited, MagicActivatedAt, MagicCompletedAt ├── WalletMode.cs → enum جدید (Normal=0, Magic=1) ├── TransactionType.cs → + MagicWalletDeposit=14, MagicWalletBonus=15 ├── SystemConstants.cs → + MagicWalletMultiplier, MagicWalletMaxDeposit, MagicWalletMaxCredit ├── UserWalletConfiguration.cs → EF config برای فیلدهای جدید ├── ClubMembershipCycle.cs → 🆕 entity جدید (حل مشکل تاریخ کمیسیون) ├── ClubMembershipCycleConfiguration.cs → EF config ├── Migration: AddMagicWalletFields → dotnet ef migrations add └── Migration: AddClubMembershipCycle → dotnet ef migrations add + data seed ``` **تست:** Migration اجرا بشه، فیلدها در DB ایجاد بشن، default‌ها درست باشن. هر ClubMembership موجود یه رکورد Cycle=1 داشته باشه. --- ### فاز ۲ — Trigger ورود/خروج Magic (روز ۲) **هدف:** State Machine خودکار ``` فایل‌های تغییری: ├── SubmitShopBuyOrderCommandHandler.cs │ ├── بعد از کسر Balance: check ورود به Magic │ └── بعد از کسر Balance: check خروج از Magic │ ├── ActivateClubMembershipCommandHandler.cs │ ├── ActivatedAt فقط بار اول ست بشه (دیگه overwrite نشه) │ └── هر بار یک ClubMembershipCycle جدید اضافه بشه │ ├── (Optional) Domain Event: WalletModeChangedEvent │ └── برای لاگ و نوتیفیکیشن │ └── User.cs (یا UserWallet) └── + PurchaseCycleCount (int) — تعداد دور خرید پکیج ``` **تست:** - سناریو ۱: Balance=0 بعد از خرید → WalletMode=Magic ✅ - سناریو ۲: بدون پکیج + Balance=0 → نباید Magic بشه ❌ - سناریو ۳: Magic + Balance=0 + **TotalDeposited=50M** (سقف پر نشده) → **هنوز Magic!** نباید خارج بشه ❌ - سناریو ۴: Magic + Balance=0 + **TotalDeposited=100M** (سقف پر) → خروج ✅ - سناریو ۵: Magic + Balance=30M + TotalDeposited=100M → **هنوز Magic!** (بالانس داره) ❌ - سناریو ۶: خروج از Magic → خرید مجدد پکیج → Balance=0 → Magic مجدد با **سقف ریست‌شده** ✅ - سناریو ۷: دور دوم → TotalDeposited, TotalCredited = 0 (ریست) ✅ --- ### فاز ۳ — API شارژ جادویی (روز ۳-۴) **هدف:** مسیر کامل شارژ از درگاه با ضریب ×2.5 ``` فایل‌های جدید: ├── InitiateMagicChargeCommand.cs ├── InitiateMagicChargeCommandHandler.cs ├── InitiateMagicChargeCommandValidator.cs ├── VerifyMagicChargeCommand.cs ├── VerifyMagicChargeCommandHandler.cs ├── MagicWalletController.cs → GET /api/wallet/verify-magic-charge │ ├── userwallet.proto → + InitiateMagicCharge, GetMagicWalletStatus RPCs └── UserWalletService.cs → implement new RPCs نکات مهم: ├── هر شارژ = ۲ تراکنش (Deposit + Bonus) ├── هر شارژ = ۱ WalletChangeLog (اجباری) ├── Validation: WalletMode==Magic && TotalDeposited+Amount <= Cap └── Callback: /api/wallet/verify-magic-charge → redirect FrontOffice ``` **تست:** - واریز 10M → Balance += 25M, TotalDeposited += 10M ✅ - واریز بیشتر از سقف → خطا ❌ - واریز در Normal Mode → خطا ❌ - ۲ تراکنش + ۱ لاگ ثبت شده ✅ --- ### فاز ۴ — غیرفعال‌سازی کمیسیون + تاریخ Cycle (روز ۴.۵) **هدف:** کاربرهای Magic از کمیسیون خارج بشن + تاریخ کمیسیون از Cycle بخونه ``` فایل‌های تغییری: ├── CalculateWeeklyBalancesCommandHandler.cs │ ├── فیلتر: WHERE wallet.WalletMode != Magic │ └── تاریخ: ActivatedAt → ClubMembershipCycle.PackagePurchasedAt │ ├── sp_CalculateWeeklyBalances.sql │ ├── + JOIN UserWallets WHERE WalletMode = 0 │ └── WHERE cm.ActivatedAt → cc.PackagePurchasedAt (AND cc.IsCurrentCycle = 1) │ └── WeekRepository (اگه date range query داره) └── آپدیت query ``` **تست:** - کاربر Magic در محاسبات هفتگی شرکت نکنه ✅ - کاربر دور ۲ (پکیج مجدد): با تاریخ PackagePurchasedAt جدید امتیاز بگیره ✅ - تاریخ اصلی ActivatedAt تغییر نکرده باشه ✅ --- ### فاز ۵ — صفحات FrontOffice (روز ۵-۶) **هدف:** UI شارژ جادویی + نمایش وضعیت ``` فایل‌های جدید: ├── Pages/Profile/MagicWallet.razor → فرم شارژ + پروگرس‌بار سقف ├── Pages/Profile/MagicWallet.razor.cs → code-behind ├── Pages/Profile/MagicPaymentCallback.razor → نتیجه پرداخت └── Pages/Profile/MagicPaymentCallback.razor.cs فایل‌های تغییری: ├── WalletService.cs → + InitiateMagicChargeAsync, GetMagicWalletStatusAsync ├── RouteConstants.cs → + MagicWallet, MagicPaymentCallback ├── Pages/Profile/Index.razor → بنر Magic Mode ├── Pages/Profile/Wallet.razor → پروگرس سقف + لینک شارژ └── NavMenu / Sidebar → لینک شرطی به صفحه جادویی ``` **UI شارژ جادویی:** ``` ┌──────────────────────────────────────────────┐ │ 🪄 کیف‌پول جادویی │ │ │ │ وضعیت: فعال ✅ │ │ مجموع واریزی: 30M / 100M تومان │ │ ██████████░░░░░░░░░░░░░░░░░░░░ 30% │ │ مجموع اعتبار دریافتی: 75M تومان │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ مبلغ واریز: [________] تومان │ │ │ │ اعتبار دریافتی: 0 × 2.5 = 0 تومان │ │ │ │ باقیمانده سقف: 70M تومان │ │ │ │ │ │ │ │ [ 🔒 پرداخت از درگاه ] │ │ │ └──────────────────────────────────────┘ │ └──────────────────────────────────────────────┘ ``` --- ### فاز ۶ — محدودیت خرید مجدد پکیج (روز ۷) **هدف:** بعد از Magic فقط IPG مجاز باشه ``` فایل‌های تغییری: ├── CheckAndProcessDayaLoansCommandHandler.cs │ └── if PurchaseCycleCount > 0 → reject │ ├── Package Purchase UI (FrontOffice) │ └── if PurchaseCycleCount > 0 → hide Daya button │ └── ActivateClubMembershipCommandHandler.cs └── if WalletMode == Magic → "ابتدا جادویی تمام شود" ``` --- ## Checklist پیاده‌سازی - [ ] **فاز ۱:** WalletMode enum - [ ] **فاز ۱:** UserWallet entity + 5 فیلد جدید - [ ] **فاز ۱:** ClubMembershipCycle entity (جدید) - [ ] **فاز ۱:** TransactionType + 2 مقدار - [ ] **فاز ۱:** SystemConstants + 3 ثابت - [ ] **فاز ۱:** EF Configuration (UserWallet + ClubMembershipCycle) - [ ] **فاز ۱:** Migration: AddMagicWalletFields - [ ] **فاز ۱:** Migration: AddClubMembershipCycle + data seed - [ ] **فاز ۲:** Trigger ورود Magic (SubmitShopBuyOrder) - [ ] **فاز ۲:** Trigger خروج Magic - [ ] **فاز ۲:** ActivateClubMembership → ActivatedAt نگه‌داشته بشه + Cycle جدید - [ ] **فاز ۲:** PurchaseCycleCount - [ ] **فاز ۳:** InitiateMagicChargeCommand + Handler - [ ] **فاز ۳:** VerifyMagicChargeCommand + Handler - [ ] **فاز ۳:** MagicWalletController (HTTP callback) - [ ] **فاز ۳:** gRPC Proto + Service - [ ] **فاز ۳:** ۲ تراکنش + ۱ لاگ (اجباری) - [ ] **فاز ۴:** فیلتر کمیسیون Magic (C# handler + SP) - [ ] **فاز ۴:** تاریخ کمیسیون: ActivatedAt → Cycle.PackagePurchasedAt (C# + SP) - [ ] **فاز ۵:** MagicWallet.razor - [ ] **فاز ۵:** MagicPaymentCallback.razor - [ ] **فاز ۵:** WalletService gRPC client - [ ] **فاز ۵:** Profile + Wallet page updates - [ ] **فاز ۶:** Daya restriction - [ ] **فاز ۶:** Club activation restriction --- ## وابستگی مستقل: تکمیل ChargeDiscountWallet > ⚠️ **این کار ربطی به کیف‌پول جادویی ندارد** و باید مستقل انجام شود. ``` مشکل فعلی: ├── ChargeDiscountWalletCommandHandler — CQRS handler موجوده ✅ ├── VerifyDiscountWalletChargeCommandHandler — موجوده ✅ ├── Callback URL = "/api/wallet/verify-discount-charge" — ست شده ├── HTTP Controller endpoint — ❌ وجود ندارد ├── gRPC RPC — ❌ در proto تعریف نشده └── FrontOffice page — ❌ صفحه شارژ وجود ندارد کار لازم: ├── WalletController.cs → GET /api/wallet/verify-discount-charge ├── userwallet.proto → rpc ChargeDiscountWallet ├── UserWalletService.cs → implement RPC ├── FrontOffice → صفحه شارژ DiscountBalance + callback └── تست end-to-end ```