diff --git a/src/FrontOffice.Main/ConfigureServices.cs b/src/FrontOffice.Main/ConfigureServices.cs index 702733a..f71e4db 100644 --- a/src/FrontOffice.Main/ConfigureServices.cs +++ b/src/FrontOffice.Main/ConfigureServices.cs @@ -15,7 +15,7 @@ using CMSMicroservice.Protobuf.Protos.User; using CMSMicroservice.Protobuf.Protos.UserCarts; using CMSMicroservice.Protobuf.Protos.UserOrder; using CMSMicroservice.Protobuf.Protos.UserWallet; -using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog; +using CMSMicroservice.Protobuf.Protos.UserWalletHistory; using CMSMicroservice.Protobuf.Protos.UserAddress; using CMSMicroservice.Protobuf.Protos.Configuration; using CMSMicroservice.Protobuf.Protos.NetworkMembership; @@ -128,7 +128,7 @@ public static class ConfigureServices services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); - services.AddScoped(CreateAuthenticatedClient); + services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj index cbd5f4e..61b82ff 100644 --- a/src/FrontOffice.Main/FrontOffice.Main.csproj +++ b/src/FrontOffice.Main/FrontOffice.Main.csproj @@ -11,7 +11,7 @@ - + @@ -56,12 +56,6 @@ .dockerignore - - - - - - diff --git a/src/FrontOffice.Main/NuGet.config b/src/FrontOffice.Main/NuGet.config index c53f59f..6402f43 100644 --- a/src/FrontOffice.Main/NuGet.config +++ b/src/FrontOffice.Main/NuGet.config @@ -2,6 +2,8 @@ + + diff --git a/src/FrontOffice.Main/Pages/Checkout.razor b/src/FrontOffice.Main/Pages/Checkout.razor index 6662eb7..79173be 100644 --- a/src/FrontOffice.Main/Pages/Checkout.razor +++ b/src/FrontOffice.Main/Pages/Checkout.razor @@ -27,6 +27,11 @@ جزئیات پکیج + @* ── G2: توضیح خرید پکیج (Q28) ── *@ + + با خرید این پکیج، سقف پاداش هفتگی، ضریب کیف‌پول جادویی و دسترسی به فیچرهای اختصاصی برای شما فعال می‌شود. + + @if (_selectedPackage != null) { @@ -207,11 +212,32 @@ FullWidth="true" StartIcon="@Icons.Material.Filled.Payment" OnClick="ProcessPayment" - Disabled="@(!CanProceedToPayment || _isProcessingPayment)" + Disabled="@(!CanProceedToPayment || _isProcessingPayment || !_selectedPackage.SupportsDirectPurchase)" Class="mt-2"> @(_isProcessingPayment ? "در حال پردازش..." : "پرداخت آنلاین") + @if (_selectedPackage.SupportsDayaPurchase) + { + + تأمین اعتبار الماسی دایا + + } + + @if (!_selectedPackage.SupportsDirectPurchase && !_selectedPackage.SupportsDayaPurchase) + { + + این پکیج در حال حاضر قابل خرید نیست. + + } + @if (!CanProceedToPayment) { diff --git a/src/FrontOffice.Main/Pages/Checkout.razor.cs b/src/FrontOffice.Main/Pages/Checkout.razor.cs index b132c8d..253feef 100644 --- a/src/FrontOffice.Main/Pages/Checkout.razor.cs +++ b/src/FrontOffice.Main/Pages/Checkout.razor.cs @@ -1,11 +1,9 @@ using CMSMicroservice.Protobuf.Protos.Package; -using CMSMicroservice.Protobuf.Protos.Transactions; using CMSMicroservice.Protobuf.Protos.UserAddress; -using CMSMicroservice.Protobuf.Protos.UserOrder; using FrontOffice.Main.Utilities; -using Google.Protobuf.WellKnownTypes; using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; using MudBlazor; using Severity = MudBlazor.Severity; @@ -13,10 +11,9 @@ namespace FrontOffice.Main.Pages; public partial class Checkout { + [Inject] private PackageService PackageService { get; set; } = default!; [Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!; [Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!; - [Inject] private UserOrderContract.UserOrderContractClient UserOrderContract { get; set; } = default!; - [Inject] private TransactionsContract.TransactionsContractClient TransactionContract { get; set; } = default!; [Parameter] public long? PackageId { get; set; } @@ -52,15 +49,18 @@ public partial class Checkout { try { - var response = await PackageClient.GetPackageAsync(new() { Id = PackageId.Value }); - if (response != null) + var packages = await PackageService.GetAllPackagesAsync(); + var pkg = packages.FirstOrDefault(p => p.Id == PackageId.Value); + if (pkg != null) { _selectedPackage = new Pack( - Id: response.Id, - Title: response.Title, - Body: response.Description, - Image: response.ImagePath ?? string.Empty, - Price: response.Price + Id: pkg.Id, + Title: pkg.Title, + Body: pkg.Description, + Image: pkg.ImageUrl, + Price: pkg.Price, + SupportsDirectPurchase: pkg.SupportsDirectPurchase, + SupportsDayaPurchase: pkg.SupportsDayaPurchase ); _finalPrice = _selectedPackage.Price; } @@ -173,9 +173,6 @@ public partial class Checkout private async Task ProcessPayment() { - Snackbar.Add("درگاه پرداخت متصل نیست! لطفا در زمان دیگری مجددا تلاش فرمایید!", Severity.Warning); - return; - if (!CanProceedToPayment || _selectedPackage == null || _selectedAddress == null) { Snackbar.Add("لطفاً پکیج و آدرس را انتخاب کنید.", Severity.Warning); @@ -186,33 +183,26 @@ public partial class Checkout try { - // Step 1: Create payment request - var paymentRequest = new CustomerPaymentRequestRequest + var response = await PackageClient.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest { - Amount = _finalPrice, - CallbackUrl = $"{Navigation.BaseUri}checkout/callback", - Description = $"خرید پکیج {_selectedPackage.Title}", - Currency = CurrencyEnum.Irt, - Type = TransactionTypeEnum.Real - }; - - var paymentResponse = await TransactionContract.CustomerPaymentRequestAsync(paymentRequest); - - if (string.IsNullOrEmpty(paymentResponse.PaymentGWUrl)) - Snackbar.Add("آدرس درگاه پرداخت دریافت نشد.", Severity.Error); - - // Step 2: Create user order - var orderRequest = new CreateNewUserOrderRequest - { - Amount = _finalPrice, PackageId = _selectedPackage.Id, - PaymentStatus = CMSMicroservice.Protobuf.Protos.PaymentStatus.Pending - }; + PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway + }); - var orderResponse = await UserOrderContract.CreateNewUserOrderAsync(orderRequest); + if (!response.Success) + { + Snackbar.Add(response.Message ?? "خطا در آغاز فرآیند پرداخت", Severity.Error); + return; + } - // Step 3: Redirect to payment gateway - Navigation.NavigateTo(paymentResponse.PaymentGWUrl); + if (!string.IsNullOrEmpty(response.PaymentGatewayUrl)) + { + Navigation.NavigateTo(response.PaymentGatewayUrl, forceLoad: true); + } + else + { + Snackbar.Add("خطا در دریافت آدرس درگاه پرداخت", Severity.Error); + } } catch (Exception ex) { @@ -225,5 +215,17 @@ public partial class Checkout } } - private record Pack(long Id, string Title, string Body, string Image, long Price); + private async Task DayaLoanPayment() + { + if (_selectedAddress == null) + { + Snackbar.Add("لطفاً آدرس را انتخاب کنید.", Severity.Warning); + return; + } + + var url = "https://dayadiamond.ir/profile/creditpurchase/?merchantcode=56146364"; + await JSRuntime.InvokeVoidAsync("open", url, "_blank"); + } + + private record Pack(long Id, string Title, string Body, string Image, long Price, bool SupportsDirectPurchase = true, bool SupportsDayaPurchase = false); } \ No newline at end of file diff --git a/src/FrontOffice.Main/Pages/Club/Components/ActivationSection.razor b/src/FrontOffice.Main/Pages/Club/Components/ActivationSection.razor index f25c29c..f103b89 100644 --- a/src/FrontOffice.Main/Pages/Club/Components/ActivationSection.razor +++ b/src/FrontOffice.Main/Pages/Club/Components/ActivationSection.razor @@ -3,7 +3,12 @@ فعال‌سازی یا تمدید عضویت - + + @* ── G7: راهنمای هزینه فعال‌سازی (Q28) ── *@ + + هزینه تخمینی بر اساس پکیج انتخابی محاسبه می‌شود. هر پکیج قیمت و شرایط فعال‌سازی متفاوتی دارد. + + diff --git a/src/FrontOffice.Main/Pages/Club/Components/ActivationSection.razor.cs b/src/FrontOffice.Main/Pages/Club/Components/ActivationSection.razor.cs index 6e60a63..496f247 100644 --- a/src/FrontOffice.Main/Pages/Club/Components/ActivationSection.razor.cs +++ b/src/FrontOffice.Main/Pages/Club/Components/ActivationSection.razor.cs @@ -10,6 +10,7 @@ public partial class ActivationSection : ComponentBase [Inject] private ClubMembershipService ClubService { get; set; } = default!; [Inject] private AuthService AuthService { get; set; } = default!; [Inject] private UserContract.UserContractClient UserContract { get; set; } = default!; + [Inject] private PackageService PackageService { get; set; } = default!; [Parameter] public EventCallback OnActivationSuccess { get; set; } @@ -21,6 +22,18 @@ public partial class ActivationSection : ComponentBase private long _packageId = 1; private int _durationMonths = 1; private string? _activationCode; + private long _basePackagePrice; + + protected override async Task OnInitializedAsync() + { + try + { + var packages = await PackageService.GetAllPackagesAsync(); + var basePackage = packages.FirstOrDefault(p => p.IsBasePackage) ?? packages.FirstOrDefault(); + _basePackagePrice = basePackage?.Price ?? 0; + } + catch { /* fallback to 0 */ } + } private async Task HandleActivateAsync() { @@ -75,9 +88,8 @@ public partial class ActivationSection : ComponentBase private string GetEstimatedCost() { - // فرمول تقریبی: 56M per month (base amount from BFF implementation) - var baseAmount = 56_000_000; - var total = baseAmount * _durationMonths; + if (_basePackagePrice <= 0) return "در حال بارگذاری..."; + var total = _basePackagePrice * _durationMonths; return $"{total:N0} تومان"; } } diff --git a/src/FrontOffice.Main/Pages/Club/MembershipPage.razor b/src/FrontOffice.Main/Pages/Club/MembershipPage.razor index f551432..7cc4835 100644 --- a/src/FrontOffice.Main/Pages/Club/MembershipPage.razor +++ b/src/FrontOffice.Main/Pages/Club/MembershipPage.razor @@ -9,6 +9,11 @@ + @* ── G6: راهنمای قرارداد باشگاه (Q28) ── *@ + + قرارداد باشگاه مشتریان فقط یک‌بار امضا می‌شود و با هر بار خرید پکیج جدید نیازی به امضای مجدد نیست. + + @if (_isLoading) { diff --git a/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor b/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor index bd0dda9..a590a8f 100644 --- a/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor +++ b/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor @@ -10,10 +10,15 @@ + @* ── G5: توضیح محاسبه پاداش (Q28) ── *@ + + پاداش هفتگی بر اساس هر پکیج جداگانه محاسبه می‌شود. هر پکیج Pool پورسانت مستقل دارد و Carryover بر اساس پکیج زیرمجموعه‌هاست. + + - + - + همه در انتظار @@ -34,7 +39,16 @@ لغو شده - + + + همه پکیج‌ها + @foreach (var pkg in _packages) + { + @pkg.Title + } + + + اعمال فیلتر @@ -87,6 +101,7 @@ هفته + پکیج امتیاز مبلغ (تومان) وضعیت @@ -97,6 +112,11 @@ @context.WeekDisplayName + + + @(string.IsNullOrEmpty(context.PackageTitle) ? "-" : context.PackageTitle) + + @context.BalancesEarned @context.AmountFormatted @@ -127,6 +147,10 @@ @payout.WeekDisplayName @payout.StatusText + @if (!string.IsNullOrEmpty(payout.PackageTitle)) + { + @payout.PackageTitle + } @payout.AmountFormatted امتیاز: @payout.BalancesEarned diff --git a/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor.cs b/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor.cs index f93ee92..15e5b36 100644 --- a/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor.cs +++ b/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor.cs @@ -8,8 +8,10 @@ namespace FrontOffice.Main.Pages.Commission; public partial class CommissionDashboardPage : ComponentBase { [Inject] private CommissionService CommissionService { get; set; } = default!; + [Inject] private PackageService PackageService { get; set; } = default!; private List _payouts = new(); + private List _packages = new(); private int _totalCount; private long _totalAmount; private int _pageNumber = 1; @@ -19,12 +21,26 @@ public partial class CommissionDashboardPage : ComponentBase private WeekSelector? _weekSelector; private WeekDefinitionDto? _selectedWeekDefinition; private string _filterStatus = string.Empty; + private long? _filterPackageId; protected override async Task OnInitializedAsync() { + await LoadPackagesAsync(); await LoadPayoutsAsync(); } + private async Task LoadPackagesAsync() + { + try + { + _packages = await PackageService.GetAllPackagesAsync(); + } + catch + { + _packages = new List(); + } + } + private async Task LoadPayoutsAsync() { try @@ -35,7 +51,8 @@ public partial class CommissionDashboardPage : ComponentBase weekDefinitionId, _filterStatus, _pageNumber, - _pageSize + _pageSize, + _filterPackageId ); _payouts = result.Payouts; _totalCount = result.TotalCount; @@ -61,6 +78,7 @@ public partial class CommissionDashboardPage : ComponentBase { _selectedWeekDefinition = null; _filterStatus = string.Empty; + _filterPackageId = null; _pageNumber = 1; await LoadPayoutsAsync(); } diff --git a/src/FrontOffice.Main/Pages/Commission/WeeklyBalancePage.razor b/src/FrontOffice.Main/Pages/Commission/WeeklyBalancePage.razor index 0e7d9cb..869ec0d 100644 --- a/src/FrontOffice.Main/Pages/Commission/WeeklyBalancePage.razor +++ b/src/FrontOffice.Main/Pages/Commission/WeeklyBalancePage.razor @@ -12,7 +12,7 @@ - + - @* *@ - @* *@ - @* هفته جاری *@ - @* *@ - @* *@ + + + همه پکیج‌ها + @foreach (var pkg in _packages) + { + @pkg.Title + } + + پایان: @_weeklyBalance.EndDatePersian + @if (!string.IsNullOrEmpty(_weeklyBalance.PackageTitle)) + { + + @_weeklyBalance.PackageTitle + + } diff --git a/src/FrontOffice.Main/Pages/Commission/WeeklyBalancePage.razor.cs b/src/FrontOffice.Main/Pages/Commission/WeeklyBalancePage.razor.cs index 2323e76..d7c0892 100644 --- a/src/FrontOffice.Main/Pages/Commission/WeeklyBalancePage.razor.cs +++ b/src/FrontOffice.Main/Pages/Commission/WeeklyBalancePage.razor.cs @@ -8,6 +8,7 @@ namespace FrontOffice.Main.Pages.Commission; public partial class WeeklyBalancePage : ComponentBase { [Inject] private CommissionService CommissionService { get; set; } = default!; + [Inject] private PackageService PackageService { get; set; } = default!; [Inject] private ISnackbar SnackbarService { get; set; } = default!; [Parameter] @@ -17,6 +18,8 @@ public partial class WeeklyBalancePage : ComponentBase private WeekSelector? _weekSelector; private WeekDefinitionDto? _selectedWeekDefinition; private WeeklyBalanceDto? _weeklyBalance; + private List _packages = new(); + private long? _filterPackageId; private bool _isLoading = true; private bool _hasError = false; @@ -34,6 +37,8 @@ public partial class WeeklyBalancePage : ComponentBase { if (firstRender) { + await LoadPackagesAsync(); + // Wait for WeekSelector to initialize and load its cached weeks if (_weekSelector != null) { @@ -88,7 +93,7 @@ public partial class WeeklyBalancePage : ComponentBase long? weekDefinitionId = _selectedWeekDefinition?.Id; - _weeklyBalance = await CommissionService.GetMyWeeklyBalanceAsync(weekDefinitionId); + _weeklyBalance = await CommissionService.GetMyWeeklyBalanceAsync(weekDefinitionId, _filterPackageId); // Update week display name from selected definition if available if (_selectedWeekDefinition != null && _weeklyBalance != null) @@ -113,6 +118,18 @@ public partial class WeeklyBalancePage : ComponentBase } } + private async Task LoadPackagesAsync() + { + try + { + _packages = await PackageService.GetAllPackagesAsync(); + } + catch + { + _packages = new(); + } + } + private async Task LoadCurrentWeekAsync() { if (_weekSelector != null) diff --git a/src/FrontOffice.Main/Pages/Package/MyPackages.razor b/src/FrontOffice.Main/Pages/Package/MyPackages.razor index 7c137ed..cefeb60 100644 --- a/src/FrontOffice.Main/Pages/Package/MyPackages.razor +++ b/src/FrontOffice.Main/Pages/Package/MyPackages.razor @@ -20,7 +20,7 @@ شما هنوز پکیجی خریداری نکرده‌اید - با خرید پکیج طلایی، به باشگاه مشتریان بپیوندید و از مزایای ویژه مانند پاداش هفتگی و تیم‌سازی بهره‌مند شوید. + با خرید پکیج، به باشگاه مشتریان بپیوندید و از مزایای ویژه مانند پاداش هفتگی و تیم‌سازی بهره‌مند شوید. + بعد از تکمیل چرخه کیف‌پول جادویی، می‌توانید مجدداً پکیج خریداری کرده و دور جدیدی را آغاز کنید. + + + + @* Re-Purchase / Cycle Progress Section *@ + @if (_canRepurchase) + { + + + + + 🎉 چرخه جادویی تکمیل شد! می‌توانید پکیج جدید بخرید. + + + خرید پکیج جدید + + + + + } + else if (_magicStatus != null && _magicStatus.WalletMode == 1) + { + + + + + + پیشرفت چرخه جادویی + @if (_magicStatus.PurchaseCycleCount > 0) + { + دور @(_magicStatus.PurchaseCycleCount + 1) + } + + + + + + + واریز انجام‌شده: + @FormatPrice(_magicStatus.MagicTotalDeposited) + + + سقف واریز: + @FormatPrice(_magicStatus.MagicMaxDeposit) + + + باقیمانده: + @FormatPrice(_magicStatus.MagicRemainingDeposit) + + + + @GetDepositProgress()% + + + + اعتبار دریافتی: + @FormatPrice(_magicStatus.MagicTotalCredited) + + + + + + } + @@ -49,7 +118,7 @@ نوع پکیج: - پکیج طلایی + @_userStatus.PackageTitle @@ -146,7 +215,7 @@ - مزایای پکیج طلایی + مزایای پکیج diff --git a/src/FrontOffice.Main/Pages/Package/MyPackages.razor.cs b/src/FrontOffice.Main/Pages/Package/MyPackages.razor.cs index 21ec012..4644f92 100644 --- a/src/FrontOffice.Main/Pages/Package/MyPackages.razor.cs +++ b/src/FrontOffice.Main/Pages/Package/MyPackages.razor.cs @@ -9,7 +9,9 @@ public partial class MyPackages : ComponentBase [Inject] private PackageService PackageService { get; set; } = default!; private UserPackageStatusDto? _userStatus; + private MagicWalletStatus? _magicStatus; private bool _isLoading = true; + private bool _canRepurchase = false; private List _breadcrumbItems = new() { @@ -30,6 +32,18 @@ public partial class MyPackages : ComponentBase try { _userStatus = await PackageService.GetUserPackageStatusAsync(); + + if (_userStatus?.HasPurchasedPackage == true) + { + _magicStatus = await WalletService.GetMagicWalletStatusAsync(); + + // Cycle complete: wallet back to Normal mode, at least one cycle done, + // and deposit ceiling fully used (remaining = 0) + _canRepurchase = _magicStatus != null + && _magicStatus.WalletMode == 0 // Normal mode + && _magicStatus.PurchaseCycleCount >= 1 // At least one cycle completed + && _magicStatus.MagicRemainingDeposit == 0; // Deposit ceiling used up + } } catch (Exception ex) { @@ -67,4 +81,11 @@ public partial class MyPackages : ComponentBase { return string.Format("{0:N0} تومان", price); } + + private int GetDepositProgress() + { + if (_magicStatus == null || _magicStatus.MagicMaxDeposit <= 0) return 0; + var pct = (int)((_magicStatus.MagicTotalDeposited * 100) / _magicStatus.MagicMaxDeposit); + return Math.Min(pct, 100); + } } diff --git a/src/FrontOffice.Main/Pages/Package/Packages.razor b/src/FrontOffice.Main/Pages/Package/Packages.razor index c7fb39c..5281dfa 100644 --- a/src/FrontOffice.Main/Pages/Package/Packages.razor +++ b/src/FrontOffice.Main/Pages/Package/Packages.razor @@ -5,6 +5,11 @@ + @* ── G1: راهنمای سیستم پکیج‌بیس (Q28) ── *@ + + هر پکیج ویژگی‌ها و مزایای منحصربه‌فردی دارد. با انتخاب پکیج مناسب، از سقف پاداش و ضریب کیف‌پول جادویی متفاوتی بهره‌مند می‌شوید. + + @if (_isLoading) { @@ -27,7 +32,7 @@ { - شما قبلاً پکیج طلایی را خریداری کرده‌اید. + شما قبلاً پکیج را خریداری کرده‌اید. @if (_userStatus.IsClubMemberActive) { عضویت باشگاه شما فعال است. @@ -64,20 +69,43 @@ @package.FormattedPrice - + - - - عضویت در باشگاه مشتریان - - - - دریافت پاداش هفتگی - - - - تیم‌سازی نامحدود - + @if (package.SupportsDirectPurchase) + { + + + پرداخت مستقیم + + } + @if (package.SupportsDayaPurchase) + { + + + پرداخت با اعتبار دایا + + } + @if (package.DiscountMultiplier > 0) + { + + + ضریب اعتبار: @package.DiscountMultiplier.ToString("F1")x + + } + @if (package.MagicWalletMultiplier > 0) + { + + + کیف پول جادویی: @package.MagicWalletMultiplier.ToString("F1")x + + } + @if (package.IsBasePackage) + { + + + پکیج پایه + + } @@ -100,9 +128,9 @@ - چرا پکیج طلایی؟ + چرا پکیج بخریم؟ - با خرید پکیج طلایی، علاوه بر دسترسی به محصولات ویژه، می‌توانید تیم فروش خود را بسازید + با خرید پکیج، علاوه بر دسترسی به محصولات ویژه، می‌توانید تیم فروش خود را بسازید و از پاداش‌های هفتگی بهره‌مند شوید. هر چه تیم شما گسترده‌تر، درآمد شما بیشتر! diff --git a/src/FrontOffice.Main/Pages/PackageDetail.razor.cs b/src/FrontOffice.Main/Pages/PackageDetail.razor.cs index 2ea07bf..cbe5d3c 100644 --- a/src/FrontOffice.Main/Pages/PackageDetail.razor.cs +++ b/src/FrontOffice.Main/Pages/PackageDetail.razor.cs @@ -64,36 +64,38 @@ public partial class PackageDetail : IDisposable try { - // Load package details - var packageResponse = await PackageClient.GetPackageAsync(request: new() { Id = Id}, cancellationToken: _loadCts.Token); + // Load package details via Customer RPC + var response = await PackageClient.GetCustomerPackageDetailsAsync( + new GetCustomerPackageDetailsRequest { PackageId = Id }, + cancellationToken: _loadCts.Token); - if (packageResponse != null) + if (response != null) { + // Build features from API response + var features = response.Features?.Select(f => f.Title).ToList() ?? new List(); + + // Build specifications from API features (highlighted ones) + var specs = response.Features? + .Where(f => f.IsHighlighted) + .Select(f => new Specification + { + Name = f.Title, + Value = f.Description, + Icon = string.IsNullOrEmpty(f.Icon) ? Icons.Material.Filled.Star : f.Icon + }).ToList() ?? new List(); + _package = new PackageDetailDto { - Id = packageResponse.Id, - Title = packageResponse.Title, - Body = packageResponse.Description, - Image = packageResponse.ImagePath ?? string.Empty, - Specifications = new List - { - new() { Name = "ظرفیت", Value = "تا ۲۰۰ عضو", Icon = Icons.Material.Filled.Group }, - new() { Name = "شجره‌نامه", Value = "پیشرفته", Icon = Icons.Material.Filled.AccountTree }, - new() { Name = "گزارش‌گیری", Value = "جامع", Icon = Icons.Material.Filled.Analytics }, - new() { Name = "پشتیبانی", Value = "۲۴ ساعته", Icon = Icons.Material.Filled.Support } - }, - Features = new List - { - "مدیریت تیم نامحدود", - "شجره‌نامه بصری", - "محاسبه کارمزد خودکار", - "گزارش‌های مالی", - "پشتیبانی اولویت‌دار" - }, + Id = response.Id, + Title = response.Title, + Body = response.Description, + Image = response.ImagePath ?? string.Empty, + Specifications = specs, + Features = features, Pricing = new PricingInfo { - OriginalPrice = packageResponse.Price, - FinalPrice = packageResponse.Price, + OriginalPrice = response.Price, + FinalPrice = response.Price, HasDiscount = false, DiscountPercent = 0 } diff --git a/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor b/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor index 0847eae..d523c8e 100644 --- a/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor +++ b/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor @@ -44,11 +44,11 @@ Label="مبلغ (تومان)" Variant="Variant.Outlined" Min="10_000" - Max="100_000_000" + Max="1_000_000_000" Format="N0" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Payments" - HelperText="حداقل ۱۰,۰۰۰ و حداکثر ۱۰۰,۰۰۰,۰۰۰ تومان" /> + HelperText="حداقل ۱۰,۰۰۰ و حداکثر ۱,۰۰۰,۰۰۰,۰۰۰ تومان" /> @* دکمه‌های مبلغ سریع *@ diff --git a/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor.cs b/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor.cs index 1d3ffaf..8c03390 100644 --- a/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor.cs @@ -29,10 +29,8 @@ public partial class ChargeDiscountWallet : ComponentBase try { - // تبدیل تومان به ریال - var amountInRial = _chargeAmount * 10; - - var (success, gatewayUrl, error) = await WalletService.InitiateDiscountChargeAsync(amountInRial); + // مبلغ به تومان — CMS خودش موقع ارسال به درگاه ×۱۰ می‌کنه + var (success, gatewayUrl, error) = await WalletService.InitiateDiscountChargeAsync(_chargeAmount); if (success && !string.IsNullOrEmpty(gatewayUrl)) { diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor b/src/FrontOffice.Main/Pages/Profile/Index.razor index f01a2b8..10dc3eb 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor @@ -119,7 +119,7 @@ لینک دعوت شما هنوز فعال نشده است - برای فعال‌سازی لینک دعوت، ابتدا پکیج پایه را تهیه کنید و سپس در باشگاه مشتریان عضو شوید. + برای فعال‌سازی لینک دعوت، ابتدا یکی از پکیج‌ها را تهیه کنید و سپس در باشگاه مشتریان عضو شوید. از مزایای ویژه عضویت بهره‌مند شده و با فعالیت در زمینه توسعه فروشگاه‌ها پاداش دریافت کنید. @@ -130,7 +130,7 @@ Class="rounded-pill" StartIcon="@Icons.Material.Filled.ShoppingCart" OnClick="OpenPurchaseOptions"> - شروع فرآیند تامین اعتبار + تهیه پکیج } @@ -195,88 +195,4 @@ -@* ── Purchase Options Dialog ── *@ - - - - - خرید پکیج پایه ۵۶ میلیون تومان - - - - برای خرید پکیج پایه، یکی از روش‌های زیر را انتخاب کنید: - - - - - - پرداخت مستقیم - - پرداخت آنی از طریق درگاه بانکی - @if (_purchaseCycleCount > 0) - { - - @if (_isProcessingPayment) - { - - در حال انتقال به درگاه... - } - else - { - پرداخت با کارت بانکی - } - - } - else - { - - @if (_isProcessingPayment) - { - - در حال انتقال به درگاه... - } - else - { - پرداخت با کارت بانکی - } - - } - - - - @if (_purchaseCycleCount == 0) - { - - - - - اعتبار الماسی دایا - - تأمین اعتبار از طریق خرید توکن الماس و دریافت وام - - تأمین اعتبار الماسی - - - - } - else - { - - از دور دوم به بعد، خرید پکیج فقط از طریق درگاه بانکی امکان‌پذیر است. - - } - - - بستن - - - - diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs index f64e0d5..f9206ad 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs @@ -42,8 +42,7 @@ public partial class Index private bool _isClubMemberActive; private bool CanShowReferralLink => _hasPurchasedPackage && _isClubMemberActive; - // Purchase Options Bottom Sheet - private bool _showPurchaseBottomSheet; + // Purchase flow private bool _isProcessingPayment; private int _purchaseCycleCount; // تعداد دورهای خرید — اگر > 0 فقط IPG مجاز @@ -55,7 +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.MagicWallet, Icons.Material.Filled.AutoAwesome, "کیف‌پول جادویی", "شارژ چند‌برابری", "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;"), @@ -394,41 +393,62 @@ public partial class Index } } - private void OpenPurchaseOptions() + private async Task OpenPurchaseOptions() { - _showPurchaseBottomSheet = true; + var options = new DialogOptions + { + MaxWidth = MaxWidth.Small, + FullWidth = true, + CloseOnEscapeKey = true, + BackdropClick = true + }; + + var parameters = new DialogParameters + { + { x => x.PurchaseCycleCount, _purchaseCycleCount } + }; + + var dialog = await DialogService.ShowAsync( + string.Empty, parameters, options); + var result = await dialog.Result; + + if (result.Canceled) return; + + if (result.Data is Shared.PackagePurchaseDialog.PackagePurchaseResult purchase) + { + switch (purchase.Method) + { + case Shared.PackagePurchaseDialog.PaymentMethodType.DirectPayment: + await ProcessDirectPayment(purchase.Package.Id); + break; + case Shared.PackagePurchaseDialog.PaymentMethodType.DayaLoan: + await ProcessDayaLoanPayment(); + break; + } + } } - private void ClosePurchaseOptions() - { - _showPurchaseBottomSheet = false; - } - - private async Task DirectPayment() + private async Task ProcessDirectPayment(long packageId) { if (_isProcessingPayment) return; - + _isProcessingPayment = true; - _showPurchaseBottomSheet = false; - + StateHasChanged(); + try { - // Create callback URL for payment verification - var callbackUrl = $"{Navigation.BaseUri}profile/payment-callback"; - - // Call BFF to initiate payment (UserId is taken from JWT token in BFF) - var response = await PackageContract.InitiateBasePackagePaymentAsync(new InitiateBasePackagePaymentRequest + var response = await PackageContract.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest { - CallbackUrl = callbackUrl + PackageId = packageId, + PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway }); - + if (!response.Success) { Snackbar.Add(response.Message ?? "خطا در آغاز فرآیند پرداخت", Severity.Error); return; } - - // Redirect to payment gateway + if (!string.IsNullOrEmpty(response.PaymentGatewayUrl)) { Navigation.NavigateTo(response.PaymentGatewayUrl, forceLoad: true); @@ -449,41 +469,34 @@ public partial class Index } } - private async Task DayaLoanPayment() + private async Task ProcessDayaLoanPayment() { - // Validate user info before redirecting if (string.IsNullOrWhiteSpace(_updateUserRequest.FirstName)) { - _showPurchaseBottomSheet = false; - Snackbar.Add($"لطفا اطلاعات شخصی خود را تکمیل کنید. (نام وارد نشده)", Severity.Error); + Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (نام وارد نشده)", Severity.Error); return; } if (string.IsNullOrWhiteSpace(_updateUserRequest.LastName)) { - _showPurchaseBottomSheet = false; - Snackbar.Add($"لطفا اطلاعات شخصی خود را تکمیل کنید. (نام خانوادگی وارد نشده)", Severity.Error); + Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (نام خانوادگی وارد نشده)", Severity.Error); return; } if (string.IsNullOrWhiteSpace(_updateUserRequest.NationalCode)) { - _showPurchaseBottomSheet = false; - Snackbar.Add($"لطفا اطلاعات شخصی خود را تکمیل کنید. (کدملی وارد نشده)", Severity.Error); + Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (کدملی وارد نشده)", Severity.Error); return; } if (_updateUserRequest.BirthDate == null) { - _showPurchaseBottomSheet = false; - Snackbar.Add($"لطفا اطلاعات شخصی خود را تکمیل کنید. (تاریخ تولد وارد نشده)", Severity.Error); + Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (تاریخ تولد وارد نشده)", Severity.Error); return; } if (!_addresses.Any()) { - _showPurchaseBottomSheet = false; - Snackbar.Add($"آدرس محل سکونت شما الزامی است!", Severity.Error); + Snackbar.Add("آدرس محل سکونت شما الزامی است!", Severity.Error); return; } - - _showPurchaseBottomSheet = false; + var url = "https://dayadiamond.ir/profile/creditpurchase/?merchantcode=56146364"; await JSRuntime.InvokeVoidAsync("open", url, "_blank"); } diff --git a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor index 1400932..f2301e4 100644 --- a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor +++ b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor @@ -36,6 +36,11 @@ { @* ═══ حالت جادویی فعال ═══ *@ + @* ── G4: هشدار تنظیمات پکیج‌بیس (Q28) ── *@ + + سقف واریز و ضریب اعتبار کیف‌پول جادویی بر اساس پکیج شما تنظیم شده است. پکیج‌های بالاتر سقف و ضریب بیشتری دارند. + + @* ─── وضعیت و پیشرفت ─── *@ @@ -92,8 +97,8 @@ - مبلغ واریزی شما ×۲.۵ به موجودی اضافه می‌شود. - مثلاً ۱۰ میلیون واریز = ۲۵ میلیون اعتبار. + مبلغ واریزی شما ×@_status.MagicMultiplier.ToString("0.#") به موجودی اضافه می‌شود. + مثلاً ۱۰ میلیون واریز = @FormatToman((long)(10_000_000 * (decimal)_status.MagicMultiplier)) اعتبار. @@ -101,7 +106,7 @@ Label="مبلغ واریز (تومان)" Variant="Variant.Outlined" Min="10_000" - Max="@(_status.MagicRemainingDeposit / 10)" + Max="@_status.MagicRemainingDeposit" Format="N0" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Payments" @@ -115,8 +120,8 @@ @FormatToman(_chargeAmount) - اعتبار دریافتی (×۲.۵): - @FormatToman((long)(_chargeAmount * 2.5m)) + اعتبار دریافتی (×@_status.MagicMultiplier.ToString("0.#")): + @FormatToman((long)(_chargeAmount * (decimal)_status.MagicMultiplier)) } @@ -125,7 +130,7 @@ @foreach (var preset in _presetAmounts) { - if (preset * 10 <= _status.MagicRemainingDeposit) + if (preset <= _status.MagicRemainingDeposit) { - هر مبلغ واریزی ×۲.۵ به موجودی اضافه می‌شود + هر مبلغ واریزی ×@_status.MagicMultiplier.ToString("0.#") به موجودی اضافه می‌شود - سقف واریز در هر دور: ۱۰۰ میلیون تومان + سقف واریز در هر دور: @FormatPrice(_status.MagicMaxDeposit) - حداکثر اعتبار: ۲۵۰ میلیون تومان (سود ۱۵۰٪) + حداکثر اعتبار: @FormatPrice(_status.MagicMaxCredit) در حالت جادویی، کمیسیون و پاداش تیمی غیرفعال است diff --git a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor.cs b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor.cs index 1b26077..16fa537 100644 --- a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor.cs @@ -46,10 +46,8 @@ public partial class MagicWallet : ComponentBase try { - // تبدیل تومان به ریال - var amountInRial = _chargeAmount * 10; - - var (success, gatewayUrl, error) = await WalletService.InitiateMagicChargeAsync(amountInRial); + // مبلغ به تومان — CMS خودش موقع ارسال به درگاه ×۱۰ می‌کنه + var (success, gatewayUrl, error) = await WalletService.InitiateMagicChargeAsync(_chargeAmount); if (success && !string.IsNullOrEmpty(gatewayUrl)) { @@ -71,8 +69,8 @@ public partial class MagicWallet : ComponentBase } } - private static string FormatPrice(long priceInRial) - => string.Format("{0:N0} تومان", priceInRial / 10); + private static string FormatPrice(long price) + => string.Format("{0:N0} تومان", price); private static string FormatToman(long toman) => string.Format("{0:N0} تومان", toman); diff --git a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor index 974a033..a3f669c 100644 --- a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor +++ b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor @@ -3,7 +3,7 @@ @using Blazored.LocalStorage @using CMSMicroservice.Protobuf.Protos.Package @using CMSMicroservice.Protobuf.Protos.User -@inject IJSRuntime JS +@using FrontOffice.Main.Utilities نتیجه پرداخت | کارا بازار سلامت @@ -21,10 +21,10 @@ پرداخت موفق @_message - @if (!string.IsNullOrEmpty(_refId)) + @if (_transactionId > 0) { - کد پیگیری: @_refId + کد ارجاعی: @_transactionId } @@ -33,13 +33,10 @@ موجودی کیف پول اصلی: @FormatPrice(_walletBalance) - - موجودی کیف پول اعتباری: @FormatPrice(_discountBalance) - - بازگشت به پروفایل + Href="@_successReturnUrl"> + @_successReturnText } else @@ -49,8 +46,8 @@ @_message - بازگشت به پروفایل + Href="@_failReturnUrl"> + @_failReturnText + /// نوع پرداخت: package (پیش‌فرض) | magic-wallet | discount-wallet | discount-order + /// + [SupplyParameterFromQuery(Name = "type")] + private string? PaymentType { get; set; } + [SupplyParameterFromQuery(Name = "orderId")] private long OrderId { get; set; } @@ -85,9 +89,14 @@ private bool _isLoading = true; private bool _isSuccess; private string _message = string.Empty; - private string? _refId; + private long _transactionId; private long _walletBalance; - private long _discountBalance; + + // دکمه‌های بازگشت بسته به نوع پرداخت + private string _successReturnUrl = "/profile"; + private string _successReturnText = "بازگشت به پروفایل"; + private string _failReturnUrl = "/profile"; + private string _failReturnText = "بازگشت به پروفایل"; protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -101,35 +110,23 @@ { try { - if (OrderId <= 0 || TransactionId <= 0 || string.IsNullOrEmpty(Authority)) - { - _isSuccess = false; - _message = "پارامترهای پرداخت نامعتبر است"; - _isLoading = false; - StateHasChanged(); - return; - } - - var response = await PackageContractClient.VerifyBasePackagePaymentAsync(new VerifyBasePackagePaymentRequest - { - OrderId = OrderId, - TransactionId = TransactionId, - PaymentSuccess = Status == "OK", - RefId = !string.IsNullOrEmpty(Authority) ? Authority : null, - Message = !string.IsNullOrEmpty(Status) ? Status : null - }); - - _isSuccess = response.Success; - _message = response.Message; - _refId = response.ReferenceCode ?? ""; - _walletBalance = response.WalletBalance; - _discountBalance = response.DiscountBalance; + // تعیین نوع پرداخت — اگر type نباشد، پیش‌فرض خرید پکیج + var type = PaymentType?.ToLowerInvariant() ?? "package"; - // اگر پرداخت موفق بود، توکن را refresh می‌کنیم - // چون PackagePurchaseMethod تغییر کرده و باید در claims جدید باشد - if (_isSuccess) + switch (type) { - await RefreshTokenAsync(); + case "magic-wallet": + await VerifyMagicWalletCharge(); + break; + case "discount-wallet": + await VerifyDiscountWalletCharge(); + break; + case "discount-order": + await VerifyDiscountOrderPayment(); + break; + default: // "package" or missing + await VerifyPackagePurchase(); + break; } } catch (Exception ex) @@ -143,10 +140,156 @@ StateHasChanged(); } } + + /// + /// تأیید خرید پکیج — رفتار فعلی + /// + private async Task VerifyPackagePurchase() + { + _successReturnUrl = "/profile"; + _successReturnText = "بازگشت به پروفایل"; + _failReturnUrl = "/profile"; + _failReturnText = "بازگشت به پروفایل"; + + if (OrderId <= 0 || string.IsNullOrEmpty(Authority)) + { + _isSuccess = false; + _message = "پارامترهای پرداخت نامعتبر است"; + return; + } + + var response = await PackageContractClient.CustomerVerifyPackagePurchaseAsync(new CustomerVerifyPackagePurchaseRequest + { + OrderId = OrderId, + Authority = Authority ?? string.Empty, + Status = Status ?? string.Empty + }); + + _isSuccess = response.Success; + _message = response.Message; + _transactionId = response.TransactionId; + + if (_isSuccess) + { + await UpdateWalletBalanceAndRefreshToken(); + } + } + + /// + /// تأیید شارژ کیف‌پول جادویی + /// + private async Task VerifyMagicWalletCharge() + { + _successReturnUrl = "/profile/magic-wallet"; + _successReturnText = "بازگشت به کیف پول جادویی"; + _failReturnUrl = "/profile/magic-wallet"; + _failReturnText = "بازگشت به کیف پول جادویی"; + + if (string.IsNullOrEmpty(Authority)) + { + _isSuccess = false; + _message = "کد Authority نامعتبر است"; + return; + } + + var (success, message) = await WalletService.VerifyMagicChargeAsync( + Authority, Status ?? "NOK"); + + _isSuccess = success; + _message = message; + + if (_isSuccess) + { + await UpdateWalletBalance(); + } + } + + /// + /// تأیید شارژ کیف پول تخفیفی + /// + private async Task VerifyDiscountWalletCharge() + { + _successReturnUrl = "/profile/charge-discount-wallet"; + _successReturnText = "بازگشت به کیف پول تخفیفی"; + _failReturnUrl = "/profile/charge-discount-wallet"; + _failReturnText = "بازگشت به کیف پول تخفیفی"; + + if (string.IsNullOrEmpty(Authority)) + { + _isSuccess = false; + _message = "کد Authority نامعتبر است"; + return; + } + + var (success, message) = await WalletService.VerifyDiscountChargeAsync( + Authority, Status ?? "NOK"); + + _isSuccess = success; + _message = message; + + if (_isSuccess) + { + await UpdateWalletBalance(); + } + } + + /// + /// تأیید پرداخت سفارش فروشگاه تخفیفی + /// + private async Task VerifyDiscountOrderPayment() + { + _successReturnUrl = $"/discount-store/order/{OrderId}"; + _successReturnText = "مشاهده سفارش"; + _failReturnUrl = $"/discount-store/order/{OrderId}"; + _failReturnText = "مشاهده سفارش"; + + if (OrderId <= 0 || string.IsNullOrEmpty(Authority)) + { + _isSuccess = false; + _message = "پارامترهای پرداخت نامعتبر است"; + return; + } + + var (success, message, _) = await DiscountOrderService.VerifyDiscountOrderPaymentAsync( + OrderId, Authority, Status ?? "NOK"); + + _isSuccess = success; + _message = message; + } + + /// + /// بروزرسانی موجودی کیف پول + /// + private async Task UpdateWalletBalance() + { + try + { + var balances = await WalletService.GetBalancesAsync(); + _walletBalance = balances.CreditBalance; + } + catch { /* اگر خطا شد، صفر نمایش بده */ } + } + + /// + /// بروزرسانی موجودی + refresh توکن (فقط برای خرید پکیج) + /// + private async Task UpdateWalletBalanceAndRefreshToken() + { + await UpdateWalletBalance(); + await RefreshTokenAsync(); + } private void RetryPayment() { - NavManager.NavigateTo("/profile"); + var type = PaymentType?.ToLowerInvariant() ?? "package"; + var url = type switch + { + "magic-wallet" => "/profile/magic-wallet", + "discount-wallet" => "/profile/charge-discount-wallet", + "discount-order" => "/discount-store", + _ => "/profile" + }; + NavManager.NavigateTo(url, forceLoad: true); } /// @@ -174,6 +317,4 @@ } private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); - - private async Task GoBack() => await JS.InvokeVoidAsync("history.back"); } diff --git a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor index d94751d..abfd053 100644 --- a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor +++ b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor @@ -112,6 +112,30 @@ else } + + @* نمایش PV سفارش *@ + @if (_pvData != null && _pvData.TotalPv > 0) + { + + + + + امتیاز فروش (PV) + + @foreach (var product in _pvData.Products) + { + + @product.ProductTitle × @product.Quantity + @string.Format("{0:N0}", product.TotalPv) PV + + } + + + جمع PV: + @string.Format("{0:N0}", _pvData.TotalPv) PV + + + } diff --git a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs index 5961f8e..a8ed6fa 100644 --- a/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/OrderDetail.razor.cs @@ -12,12 +12,17 @@ public partial class OrderDetail : ComponentBase [Parameter] public long id { get; set; } private GetUserOrderResponse? _order; + private CalculateOrderPVResponse? _pvData; private bool _loading; protected override async Task OnParametersSetAsync() { _loading = true; _order = await OrderService.GetOrderAsync(id); + if (_order != null) + { + _pvData = await OrderService.CalculateOrderPVAsync(id); + } _loading = false; } diff --git a/src/FrontOffice.Main/Shared/ClubMembershipContractDialog.razor b/src/FrontOffice.Main/Shared/ClubMembershipContractDialog.razor index 9fb7cf7..18f3ed9 100644 --- a/src/FrontOffice.Main/Shared/ClubMembershipContractDialog.razor +++ b/src/FrontOffice.Main/Shared/ClubMembershipContractDialog.razor @@ -9,6 +9,7 @@ @inject AuthService AuthService @inject ISnackbar Snackbar @inject NavigationManager Navigation +@inject PackageService PackageService @@ -123,6 +124,23 @@ private readonly Guid _signGuid = Guid.NewGuid(); private System.Timers.Timer? _timer; private const string TokenStorageKey = "auth:token"; + private string _packageDisplayName = "پکیج پایه"; + private string _packageDisplayPrice = "۵۶ میلیون تومان"; + + protected override async Task OnInitializedAsync() + { + try + { + var packages = await PackageService.GetAllPackagesAsync(); + var basePackage = packages.FirstOrDefault(p => p.IsBasePackage) ?? packages.FirstOrDefault(); + if (basePackage != null) + { + _packageDisplayName = basePackage.Title; + _packageDisplayPrice = $"{basePackage.Price:N0} تومان"; + } + } + catch { /* fallback to defaults */ } + } // این Modal غیرقابل بسته شدن است - Options در ShowAsync تنظیم می‌شوند @@ -242,7 +260,7 @@

۱-۳. پاداش معرفی: مبلغی که به ازای هر معرفی موفق به کاربر تعلق می‌گیرد.

ماده ۲ - شرایط عضویت

-

۲-۱. کاربر با پرداخت مبلغ ۵۶ میلیون تومان (پکیج پایه)، امکان عضویت در باشگاه مشتریان را خواهد داشت.

+

۲-۱. کاربر با پرداخت مبلغ {_packageDisplayPrice} ({_packageDisplayName})، امکان عضویت در باشگاه مشتریان را خواهد داشت.

۲-۲. پس از امضای این قرارداد، لینک دعوت کاربر فعال خواهد شد.

۲-۳. کاربر می‌تواند حداکثر ۲ نفر را مستقیماً دعوت کند (یک نفر در تیم اول و یک نفر در تیم دوم).

diff --git a/src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor b/src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor new file mode 100644 index 0000000..c997ff1 --- /dev/null +++ b/src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor @@ -0,0 +1,234 @@ +@using FrontOffice.Main.Utilities +@inject PackageService PackageService +@inject ISnackbar Snackbar + + + + @if (_selectedPackage != null) + { + + + انتخاب روش پرداخت + + } + else + { + + + انتخاب پکیج + + } + + + @if (_isLoading) + { + + + در حال بارگذاری پکیج‌ها... + + } + else if (_selectedPackage != null) + { + @* ── Step 2: Payment method selection ── *@ + + @* Selected Package Summary *@ + + + + + + + @_selectedPackage.Title + @_selectedPackage.FormattedPrice + + + + + + + + روش پرداخت خود را انتخاب کنید: + + + @* Direct Payment — always available *@ + + + + + + پرداخت مستقیم + پرداخت آنی از طریق درگاه بانکی + + + + + + + @* Daya Loan — only for base package AND first purchase cycle *@ + @if (_selectedPackage.SupportsDayaPurchase && PurchaseCycleCount == 0) + { + + + + + + اعتبار الماسی دایا + تأمین اعتبار از طریق خرید توکن الماس + + + + + + } + else if (!_selectedPackage.SupportsDayaPurchase) + { + + اعتبار الماسی دایا فقط برای پکیج پایه قابل استفاده است. + + } + else if (PurchaseCycleCount > 0) + { + + از دور دوم به بعد، خرید پکیج فقط از طریق درگاه بانکی امکان‌پذیر است. + + } + + } + else + { + @* ── Step 1: Package tile grid ── *@ + + + پکیج مورد نظر خود را انتخاب کنید: + + + + @foreach (var pkg in _packages) + { + + + @if (pkg.IsBasePackage) + { + پایه + } + + + + + @pkg.Title + @pkg.FormattedPrice + @if (!string.IsNullOrWhiteSpace(pkg.Description)) + { + + @((MarkupString)pkg.Description) + + } + @* Key features *@ + + @if (pkg.MagicWalletMultiplier > 0) + { + + + ضریب جادویی: @pkg.MagicWalletMultiplier.ToString("F1")x + + } + @if (pkg.DiscountMultiplier > 0) + { + + + ضریب اعتبار: @pkg.DiscountMultiplier.ToString("F1")x + + } + @if (pkg.SupportsDayaPurchase) + { + + + قابل خرید با دایا + + } + + + + + } + + + } + + + بستن + + + +@code { + [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!; + + /// Number of previous purchase cycles — controls Daya eligibility + [Parameter] public int PurchaseCycleCount { get; set; } + + private List _packages = new(); + private PackageDto? _selectedPackage; + private bool _isLoading = true; + + protected override async Task OnInitializedAsync() + { + _isLoading = true; + try + { + _packages = await PackageService.GetAllPackagesAsync(); + } + catch (Exception ex) + { + Snackbar.Add("خطا در بارگذاری پکیج‌ها", MudBlazor.Severity.Error); + } + finally + { + _isLoading = false; + } + } + + private void SelectPackage(PackageDto package) + { + _selectedPackage = package; + } + + private void BackToList() + { + _selectedPackage = null; + } + + private void SelectPaymentMethod(PaymentMethodType method) + { + var result = new PackagePurchaseResult(_selectedPackage!, method); + MudDialog.Close(DialogResult.Ok(result)); + } + + private void Cancel() => MudDialog.Cancel(); + + private static string GetTileStyle(PackageDto pkg) + { + return pkg.IsBasePackage + ? "border:2px solid var(--mud-palette-primary); background:rgba(99,102,241,.04); cursor:pointer;" + : "border:2px solid var(--mud-palette-lines-default); background:var(--mud-palette-surface); cursor:pointer;"; + } + + private static string GetAvatarStyle(PackageDto pkg) + { + return pkg.IsBasePackage + ? "background:rgba(99,102,241,.15); color:#6366f1;" + : "background:rgba(16,185,129,.12); color:#10b981;"; + } + + public enum PaymentMethodType { DirectPayment, DayaLoan } + public record PackagePurchaseResult(PackageDto Package, PaymentMethodType Method); +} diff --git a/src/FrontOffice.Main/Utilities/CommissionDtos.cs b/src/FrontOffice.Main/Utilities/CommissionDtos.cs index b546ed3..19c78e2 100644 --- a/src/FrontOffice.Main/Utilities/CommissionDtos.cs +++ b/src/FrontOffice.Main/Utilities/CommissionDtos.cs @@ -15,6 +15,8 @@ public class CommissionPayoutDto public string AmountFormatted { get; set; } = string.Empty; public int Status { get; set; } public string DatePersian { get; set; } = string.Empty; + public long PackageId { get; set; } + public string PackageTitle { get; set; } = string.Empty; // Computed properties for UI public string StatusText => Status switch @@ -69,6 +71,8 @@ public class WeeklyBalanceDto public int RightNewMembers { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } + public long PackageId { get; set; } + public string PackageTitle { get; set; } = string.Empty; // Formatted properties public string LeftBalanceFormatted => $"{LeftBalance:N0} "; diff --git a/src/FrontOffice.Main/Utilities/CommissionService.cs b/src/FrontOffice.Main/Utilities/CommissionService.cs index daea7ff..53ca510 100644 --- a/src/FrontOffice.Main/Utilities/CommissionService.cs +++ b/src/FrontOffice.Main/Utilities/CommissionService.cs @@ -78,7 +78,8 @@ public class CommissionService long? weekDefinitionId, string? status, int pageNumber, - int pageSize) + int pageSize, + long? packageId = null) { try { @@ -107,6 +108,11 @@ public class CommissionService _ => null }; } + + if (packageId.HasValue && packageId.Value > 0) + { + request.PackageId = packageId.Value; + } var response = await _client.GetMyCommissionPayoutsAsync(request); @@ -121,7 +127,9 @@ public class CommissionService TotalAmount = p.TotalAmount, AmountFormatted = p.AmountFormatted, Status = p.Status, - DatePersian = p.DatePersian + DatePersian = p.DatePersian, + PackageId = p.PackageId, + PackageTitle = p.PackageTitle }).ToList(), TotalCount = (int)(response.MetaData?.TotalCount ?? 0), PageNumber = pageNumber, @@ -145,7 +153,7 @@ public class CommissionService /// Get weekly balance details for a specific week /// Maps to: CommissionCQ.GetMyWeeklyBalances ///
- public async Task GetMyWeeklyBalanceAsync(long? weekDefinitionId = null) + public async Task GetMyWeeklyBalanceAsync(long? weekDefinitionId = null, long? packageId = null) { try { @@ -163,6 +171,11 @@ public class CommissionService { request.WeekDefinitionId=7; } + + if (packageId.HasValue && packageId.Value > 0) + { + request.PackageId = packageId.Value; + } var response = await _client.GetMyWeeklyBalancesAsync(request); @@ -183,7 +196,9 @@ public class CommissionService LeftNewMembers = balance.LeftLegNewMembers, RightNewMembers = balance.RightLegNewMembers, StartDate = balance.CalculatedAt?.ToDateTime().AddDays(-7) ?? DateTime.Now.AddDays(-7), - EndDate = balance.CalculatedAt?.ToDateTime() ?? DateTime.Now + EndDate = balance.CalculatedAt?.ToDateTime() ?? DateTime.Now, + PackageId = balance.PackageId, + PackageTitle = balance.PackageTitle }; } @@ -227,7 +242,9 @@ public class CommissionService TotalAmount = p.TotalAmount, AmountFormatted = p.AmountFormatted, Status = p.Status, - DatePersian = p.DatePersian + DatePersian = p.DatePersian, + PackageId = p.PackageId, + PackageTitle = p.PackageTitle }).ToList(); } catch diff --git a/src/FrontOffice.Main/Utilities/DiscountOrderService.cs b/src/FrontOffice.Main/Utilities/DiscountOrderService.cs index 58aa5af..53c0d75 100644 --- a/src/FrontOffice.Main/Utilities/DiscountOrderService.cs +++ b/src/FrontOffice.Main/Utilities/DiscountOrderService.cs @@ -112,6 +112,27 @@ public class DiscountOrderService catch { return false; } } + public async Task<(bool Success, string Message, long OrderId)> VerifyDiscountOrderPaymentAsync( + long orderId, string authority, string status) + { + try + { + var response = await _client.CustomerVerifyDiscountOrderPaymentAsync( + new CustomerVerifyDiscountOrderPaymentRequest + { + OrderId = orderId, + Authority = authority, + Status = status + }); + + return (response.Success, response.Message, response.OrderId); + } + catch (Grpc.Core.RpcException ex) + { + return (false, ex.Status.Detail ?? "خطا در بررسی وضعیت پرداخت", orderId); + } + } + public async Task GetUserOrdersAsync(int page = 1, int pageSize = 10) { try diff --git a/src/FrontOffice.Main/Utilities/OrderService.cs b/src/FrontOffice.Main/Utilities/OrderService.cs index 5f3ca8f..39d9cfb 100644 --- a/src/FrontOffice.Main/Utilities/OrderService.cs +++ b/src/FrontOffice.Main/Utilities/OrderService.cs @@ -97,4 +97,22 @@ public class OrderService return 0.10; } } + + /// + /// محاسبه PV سفارش + /// + public async Task CalculateOrderPVAsync(long orderId) + { + try + { + return await _userOrderContractClient.CalculateOrderPVAsync(new CalculateOrderPVRequest + { + OrderId = orderId + }); + } + catch + { + return null; + } + } } \ No newline at end of file diff --git a/src/FrontOffice.Main/Utilities/PackageService.cs b/src/FrontOffice.Main/Utilities/PackageService.cs index ba1df1c..f68850d 100644 --- a/src/FrontOffice.Main/Utilities/PackageService.cs +++ b/src/FrontOffice.Main/Utilities/PackageService.cs @@ -10,8 +10,17 @@ public record PackageDto( string Title, string Description, string ImageUrl, - long Price) + long Price, + long ActivationFee = 0, + double DiscountMultiplier = 0, + double MagicWalletMultiplier = 0, + long MagicWalletMaxDeposit = 0, + long MagicWalletMaxCredit = 0, + bool IsBasePackage = false, + bool SupportsDayaPurchase = false, + bool SupportsDirectPurchase = false) { + /// قیمت فرمت‌شده به تومان public string FormattedPrice => string.Format("{0:N0} تومان", Price); } @@ -20,6 +29,7 @@ public record PackageDto( /// public record UserPackageStatusDto( bool HasPurchasedPackage, + string? PackageTitle, string? PurchaseMethod, // DayaLoan, DirectPurchase, or null bool IsClubMemberActive, long WalletBalance, @@ -55,7 +65,15 @@ public class PackageService m.Title, m.Description, m.ImagePath, - m.Price)) + m.Price, + m.ActivationFee, + m.DiscountMultiplier, + m.MagicWalletMultiplier, + m.MagicWalletMaxDeposit, + m.MagicWalletMaxCredit, + m.IsBasePackage, + m.SupportsDayaPurchase, + m.SupportsDirectPurchase)) .ToList(); } catch (Exception ex) @@ -99,15 +117,32 @@ public class PackageService /// /// Get user's package purchase status /// - public Task GetUserPackageStatusAsync(CancellationToken ct = default) + public async Task GetUserPackageStatusAsync(CancellationToken ct = default) { - // TODO: Connect to GetUserPackageStatus RPC when available in FrontOffice.BFF - return Task.FromResult(new UserPackageStatusDto( - HasPurchasedPackage: false, - PurchaseMethod: null, - IsClubMemberActive: false, - WalletBalance: 0, - CanActivateClubMembership: false, - PurchaseDate: null)); + try + { + var response = await _client.GetUserPackageStatusAsync( + new GetUserPackageStatusRequest(), cancellationToken: ct); + + return new UserPackageStatusDto( + HasPurchasedPackage: response.HasPurchasedPackage, + PackageTitle: null, + PurchaseMethod: response.PackagePurchaseMethod, + IsClubMemberActive: response.IsClubMemberActive, + WalletBalance: response.WalletBalance, + CanActivateClubMembership: response.CanActivateClubMembership, + PurchaseDate: response.LastPurchaseDate?.ToDateTime()); + } + catch + { + return new UserPackageStatusDto( + HasPurchasedPackage: false, + PackageTitle: null, + PurchaseMethod: null, + IsClubMemberActive: false, + WalletBalance: 0, + CanActivateClubMembership: false, + PurchaseDate: null); + } } } diff --git a/src/FrontOffice.Main/Utilities/WalletService.cs b/src/FrontOffice.Main/Utilities/WalletService.cs index 6ae3128..7d6648b 100644 --- a/src/FrontOffice.Main/Utilities/WalletService.cs +++ b/src/FrontOffice.Main/Utilities/WalletService.cs @@ -1,6 +1,5 @@ using DateTimeConverterCL; using CMSMicroservice.Protobuf.Protos.UserWallet; -using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog; using Google.Protobuf.WellKnownTypes; namespace FrontOffice.Main.Utilities; @@ -33,7 +32,9 @@ public record MagicWalletStatus( long MagicRemainingDeposit, long Balance, DateTime? MagicActivatedAt, - int PurchaseCycleCount + int PurchaseCycleCount, + double MagicMultiplier = 2.5, + long MagicMaxCredit = 2_500_000_000 ); public class WalletService @@ -63,7 +64,7 @@ public class WalletService { try { - var request = new GetCustomerWalletChangeLogRequest(); + var request = new GetCustomerWalletHistoryRequest(); if (referenceId.HasValue) { request.ReferenceId = referenceId.Value; @@ -72,7 +73,7 @@ public class WalletService { request.IsIncrease = isIncrease.Value; } - var response = await _client.GetCustomerWalletChangeLogAsync(request); + var response = await _client.GetCustomerWalletHistoryAsync(request); return response.Models .Select(log => new WalletTransaction( @@ -96,7 +97,7 @@ public class WalletService } } - private static string ResolveTransactionDate(CustomerWalletChangeLogModel model) + private static string ResolveTransactionDate(CustomerWalletHistoryModel model) { if (model.CreatedAt is not null) { @@ -114,7 +115,7 @@ public class WalletService return DateTime.Now.MiladiToJalaliWithTime(); } - private static string ResolveDescription(CustomerWalletChangeLogModel model) + private static string ResolveDescription(CustomerWalletHistoryModel model) { var parts = new List(); @@ -214,7 +215,9 @@ public class WalletService response.MagicRemainingDeposit, response.Balance, activatedAt, - response.PurchaseCycleCount + response.PurchaseCycleCount, + response.MagicMultiplier > 0 ? response.MagicMultiplier : 2.5, + response.MagicMaxCredit > 0 ? response.MagicMaxCredit : 2_500_000_000 ); } catch @@ -263,6 +266,44 @@ public class WalletService } } + // ============= Wallet Verify Methods ============= + + public async Task<(bool Success, string Message)> VerifyMagicChargeAsync(string authority, string status) + { + try + { + var response = await _client.VerifyMagicChargeAsync(new VerifyWalletChargeRequest + { + Authority = authority, + Status = status + }); + + return (response.Success, response.Message); + } + catch (Grpc.Core.RpcException ex) + { + return (false, ex.Status.Detail ?? "خطا در بررسی وضعیت پرداخت"); + } + } + + public async Task<(bool Success, string Message)> VerifyDiscountChargeAsync(string authority, string status) + { + try + { + var response = await _client.VerifyDiscountChargeAsync(new VerifyWalletChargeRequest + { + Authority = authority, + Status = status + }); + + return (response.Success, response.Message); + } + catch (Grpc.Core.RpcException ex) + { + return (false, ex.Status.Detail ?? "خطا در بررسی وضعیت پرداخت"); + } + } + public int GetWalletMode() { try diff --git a/src/FrontOffice.Main/wwwroot/css/site.css b/src/FrontOffice.Main/wwwroot/css/site.css index d6d5ac7..79e629c 100644 --- a/src/FrontOffice.Main/wwwroot/css/site.css +++ b/src/FrontOffice.Main/wwwroot/css/site.css @@ -1611,3 +1611,30 @@ html, body { } /*#endregion*/ + +/* ── Package Purchase Dialog tiles ── */ +.pkg-tile { + transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease; + position: relative; +} +.pkg-tile:hover { + transform: translateY(-3px); + box-shadow: 0 6px 20px rgba(0,0,0,.08); + border-color: var(--mud-palette-primary) !important; +} +.pkg-tile-badge { + position: absolute; + top: 8px; + right: 8px; +} +.pkg-payment-option { + transition: transform .15s ease, box-shadow .15s ease; +} +.pkg-payment-option:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0,0,0,.06); +} +@media (max-width: 600px) { + .pkg-tile { padding: .8rem !important; } + .pkg-tile .mud-avatar-large { width: 48px !important; height: 48px !important; } +}