From 104e4df1a6fc8e1f0c6209ed8356fcdae59dea48 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sat, 21 Feb 2026 19:50:43 +0330 Subject: [PATCH] feat: implement Magic Wallet feature with charging and status display --- src/FrontOffice.Main/FrontOffice.Main.csproj | 4 +- .../Pages/DiscountStore/Checkout.razor | 2 +- .../Pages/DiscountStore/Checkout.razor.cs | 4 +- .../Pages/Profile/Index.razor.cs | 1 + .../Pages/Profile/MagicWallet.razor | 209 ++++++++++++++++++ .../Pages/Profile/MagicWallet.razor.cs | 79 +++++++ .../Utilities/RouteConstants.cs | 1 + src/FrontOffice.Main/Utilities/VATService.cs | 6 +- .../Utilities/WalletService.cs | 70 ++++++ 9 files changed, 368 insertions(+), 8 deletions(-) create mode 100644 src/FrontOffice.Main/Pages/Profile/MagicWallet.razor create mode 100644 src/FrontOffice.Main/Pages/Profile/MagicWallet.razor.cs diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj index a4662dc..035a7bf 100644 --- a/src/FrontOffice.Main/FrontOffice.Main.csproj +++ b/src/FrontOffice.Main/FrontOffice.Main.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor index 949e630..6158341 100644 --- a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor +++ b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor @@ -118,7 +118,7 @@ - مالیات بر ارزش افزوده (۹٪): + مالیات بر ارزش افزوده (۱۰٪): @FormatPrice(VatAmount)+ تومان diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs index 44c7153..9995b32 100644 --- a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs +++ b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs @@ -18,12 +18,12 @@ public partial class Checkout private string? _notes; private bool _placing; - private const decimal VAT_RATE = 0.09m; + private const decimal VAT_RATE = 0.10m; /// مبلغ درگاه قبل از مالیات (جمع کل - تخفیف) private long NetGatewayAmount => DiscountCart.TotalPrice - DiscountCart.TotalDiscount; - /// مالیات بر ارزش افزوده ۹٪ + /// مالیات بر ارزش افزوده ۱۰٪ private long VatAmount => (long)(NetGatewayAmount * VAT_RATE); /// مبلغ نهایی قابل پرداخت (شامل VAT) diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs index 68f8c5e..2b47695 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs @@ -54,6 +54,7 @@ public partial class Index new(RouteConstants.Profile.Tree, Icons.Material.Filled.AccountTree, "شجره‌نامه", "ساختار تیم", "background:rgba(99,102,241,.12); color:#6366f1;"), new(RouteConstants.Gateway.StoreChooser, Icons.Material.Filled.Store, "فروشگاه‌ها", "ورود به فروشگاه", "background:rgba(16,185,129,.12); color:#10b981;"), new(RouteConstants.Profile.Wallet, Icons.Material.Filled.AccountBalanceWallet, "کیف پول", "مدیریت و تاریخچه", "background:rgba(16,185,129,.12); color:#10b981;"), + new(RouteConstants.Profile.MagicWallet, Icons.Material.Filled.AutoAwesome, "کیف‌پول جادویی", "شارژ ×2.5", "background:rgba(168,85,247,.12); color:#a855f7;"), new(RouteConstants.Profile.WithdrawalRequests, Icons.Material.Filled.RequestPage, "درخواست برداشت", "ثبت و پیگیری", "background:rgba(245,158,11,.12); color:#f59e0b;"), new(RouteConstants.Club.Membership, Icons.Material.Filled.CardMembership, "باشگاه مشتریان", "عضویت و مزایا", "background:rgba(168,85,247,.12); color:#a855f7;"), new(RouteConstants.Network.Statistics, Icons.Material.Filled.Groups, "آمار باشگاه", "جزئیات باشگاه", "background:rgba(59,130,246,.12); color:#3b82f6;"), diff --git a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor new file mode 100644 index 0000000..a5fd7de --- /dev/null +++ b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor @@ -0,0 +1,209 @@ +@attribute [Route(RouteConstants.Profile.MagicWallet)] +@attribute [Authorize] + +کیف‌پول جادویی + + + + + + @if (_isLoading) + { + + + در حال بارگذاری... + + } + else if (_status.WalletMode != 1) + { + @* ═══ حالت عادی — Magic فعال نیست ═══ *@ + + + کیف‌پول جادویی فعال نیست + + برای فعال شدن کیف‌پول جادویی، ابتدا پکیج پایه را خریداری کنید و سپس + تمام موجودی عادی خود را از فروشگاه خرج کنید. وقتی موجودی عادی به صفر برسد، + کیف‌پول جادویی فعال می‌شود. + + + بازگشت به کیف پول + + + } + else + { + @* ═══ حالت جادویی فعال ═══ *@ + + @* ─── وضعیت و پیشرفت ─── *@ + + + + + حالت جادویی فعال + + + + + موجودی فعلی + @FormatPrice(_status.Balance) + + + مجموع واریزی + @FormatPrice(_status.MagicTotalDeposited) + + + مجموع اعتبار + @FormatPrice(_status.MagicTotalCredited) + + + سقف باقیمانده + @FormatPrice(_status.MagicRemainingDeposit) + + + + @* نوار پیشرفت *@ + + + پیشرفت سقف واریز + @(_progressPercent.ToString("0"))% + + + + + @if (_status.MagicActivatedAt.HasValue) + { + + فعال از: @_status.MagicActivatedAt.Value.MiladiToJalaliWithTime() + + } + + + + @* ─── فرم شارژ ─── *@ + @if (_status.MagicRemainingDeposit > 0) + { + + + + شارژ کیف‌پول جادویی + + + + مبلغ واریزی شما ×۲.۵ به موجودی اضافه می‌شود. + مثلاً ۱۰ میلیون واریز = ۲۵ میلیون اعتبار. + + + + + + @if (_chargeAmount > 0) + { + + + مبلغ واریز: + @FormatToman(_chargeAmount) + + + اعتبار دریافتی (×۲.۵): + @FormatToman((long)(_chargeAmount * 2.5m)) + + + } + + @* دکمه‌های مبلغ سریع *@ + + @foreach (var preset in _presetAmounts) + { + if (preset * 10 <= _status.MagicRemainingDeposit) + { + + @FormatToman(preset) + + } + } + + + + @if (_isProcessing) + { + + در حال انتقال به درگاه... + } + else + { + پرداخت از درگاه + } + + + + } + else + { + + سقف شارژ جادویی در این دور پر شده است. + با خرج کردن موجودی و خرید مجدد پکیج، سقف ریست می‌شود. + + } + + @* ─── قوانین ─── *@ + + + + + هر مبلغ واریزی ×۲.۵ به موجودی اضافه می‌شود + + + سقف واریز در هر دور: ۱۰۰ میلیون تومان + + + حداکثر اعتبار: ۲۵۰ میلیون تومان (سود ۱۵۰٪) + + + در حالت جادویی، کمیسیون و پورسانت غیرفعال است + + + بعد از اتمام سقف و خرج موجودی، با خرید مجدد پکیج دور جدید شروع می‌شود + + + + + } + + @* ─── نتیجه پرداخت (اگه از callback برگشته) ─── *@ + @if (_paymentResult != null) + { + + @if (_paymentResult == "success") + { + شارژ جادویی با موفقیت انجام شد! ✅ + } + else + { + پرداخت ناموفق بود. لطفاً دوباره تلاش کنید. + } + + } + + diff --git a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor.cs b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor.cs new file mode 100644 index 0000000..d5bf1d8 --- /dev/null +++ b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor.cs @@ -0,0 +1,79 @@ +using DateTimeConverterCL; +using FrontOffice.Main.Utilities; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace FrontOffice.Main.Pages.Profile; + +public partial class MagicWallet : ComponentBase +{ + private MagicWalletStatus _status = new(0, 0, 0, 0, 0, 0, null); + private bool _isLoading = true; + private bool _isProcessing; + private long _chargeAmount; + private double _progressPercent; + private string? _paymentResult; + + private readonly long[] _presetAmounts = { 1_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000, 100_000_000 }; + + [SupplyParameterFromQuery(Name = "payment")] + public string? PaymentQueryParam { get; set; } + + protected override async Task OnInitializedAsync() + { + _paymentResult = PaymentQueryParam; + await LoadStatus(); + } + + private async Task LoadStatus() + { + _isLoading = true; + _status = await WalletService.GetMagicWalletStatusAsync(); + + _progressPercent = _status.MagicMaxDeposit > 0 + ? (double)_status.MagicTotalDeposited / _status.MagicMaxDeposit * 100 + : 0; + + _isLoading = false; + } + + private async Task StartMagicCharge() + { + if (_chargeAmount <= 0 || _isProcessing) return; + + _isProcessing = true; + StateHasChanged(); + + try + { + // تبدیل تومان به ریال + var amountInRial = _chargeAmount * 10; + + var (success, gatewayUrl, error) = await WalletService.InitiateMagicChargeAsync(amountInRial); + + if (success && !string.IsNullOrEmpty(gatewayUrl)) + { + Navigation.NavigateTo(gatewayUrl, forceLoad: true); + } + else + { + Snackbar.Add(error ?? "خطا در ایجاد درخواست پرداخت", Severity.Error); + } + } + catch (Exception ex) + { + Snackbar.Add($"خطا: {ex.Message}", Severity.Error); + } + finally + { + _isProcessing = false; + StateHasChanged(); + } + } + + private static string FormatPrice(long priceInRial) + => string.Format("{0:N0} تومان", priceInRial / 10); + + private static string FormatToman(long toman) + => string.Format("{0:N0} تومان", toman); +} diff --git a/src/FrontOffice.Main/Utilities/RouteConstants.cs b/src/FrontOffice.Main/Utilities/RouteConstants.cs index 7fd1f65..a1de966 100644 --- a/src/FrontOffice.Main/Utilities/RouteConstants.cs +++ b/src/FrontOffice.Main/Utilities/RouteConstants.cs @@ -22,6 +22,7 @@ public static class RouteConstants public const string ChangePassword = "/profile/change-password"; public const string Tree = "/profile/tree"; public const string Wallet = "/profile/wallet"; + public const string MagicWallet = "/profile/magic-wallet"; public const string WithdrawalRequests = "/profile/withdrawal-requests"; } diff --git a/src/FrontOffice.Main/Utilities/VATService.cs b/src/FrontOffice.Main/Utilities/VATService.cs index f36f9ed..6e90670 100644 --- a/src/FrontOffice.Main/Utilities/VATService.cs +++ b/src/FrontOffice.Main/Utilities/VATService.cs @@ -17,9 +17,9 @@ public class VATService private const string VAT_PERCENTAGE_KEY = "vat_percentage"; private const string VAT_DATE_KEY = "vat_date"; - // مقادیر پیش‌فرض - private double _vatRate = 0.0999; - private int _vatPercentage = 99; + // مقادیر پیش‌فرض (10%) + private double _vatRate = 0.10; + private int _vatPercentage = 10; private bool _isEnabled = true; private bool _isLoaded = false; diff --git a/src/FrontOffice.Main/Utilities/WalletService.cs b/src/FrontOffice.Main/Utilities/WalletService.cs index 890f244..ea77c60 100644 --- a/src/FrontOffice.Main/Utilities/WalletService.cs +++ b/src/FrontOffice.Main/Utilities/WalletService.cs @@ -25,6 +25,16 @@ public enum WithdrawalMethodClient } public record WithdrawalSettings(long MinWithdrawalAmount); +public record MagicWalletStatus( + int WalletMode, + long MagicTotalDeposited, + long MagicTotalCredited, + long MagicMaxDeposit, + long MagicRemainingDeposit, + long Balance, + DateTime? MagicActivatedAt +); + public class WalletService { private readonly CMSMicroservice.Protobuf.Protos.UserWallet.UserWalletContract.UserWalletContractClient _client; @@ -183,4 +193,64 @@ public class WalletService return new WithdrawalSettings(0); } } + + // ============= Magic Wallet ============= + + public async Task GetMagicWalletStatusAsync() + { + try + { + var response = await _client.GetMagicWalletStatusAsync(new Empty()); + DateTime? activatedAt = response.MagicActivatedAt != null + ? response.MagicActivatedAt.ToDateTime().ToLocalTime() + : null; + + return new MagicWalletStatus( + response.WalletMode, + response.MagicTotalDeposited, + response.MagicTotalCredited, + response.MagicMaxDeposit, + response.MagicRemainingDeposit, + response.Balance, + activatedAt + ); + } + catch + { + return new MagicWalletStatus(0, 0, 0, 0, 0, 0, null); + } + } + + public async Task<(bool Success, string? GatewayUrl, string? Error)> InitiateMagicChargeAsync(long amount) + { + try + { + var response = await _client.InitiateMagicChargeAsync(new InitiateMagicChargeRequest + { + Amount = amount + }); + + if (response.IsSuccess) + return (true, response.GatewayUrl, null); + + return (false, null, response.ErrorMessage); + } + catch (Grpc.Core.RpcException ex) + { + return (false, null, ex.Status.Detail ?? "خطا در ارتباط با سرور"); + } + } + + public int GetWalletMode() + { + try + { + var response = _client.GetCustomerWallet(new Empty()); + return response.WalletMode; + } + catch + { + return 0; + } + } }