From 86f43d8fdd1f281e768d34a4355bd382ffc23ad7 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 00:02:31 +0330 Subject: [PATCH 01/19] =?UTF-8?q?chore:=20prevent=20appsettings.Production?= =?UTF-8?q?.json=20from=20merging=20=E2=80=94=20keep=20ours=20strategy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..82b5185 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Keep production config from current branch during merges — never overwrite +src/FrontOffice.Main/appsettings.Production.json merge=ours From ab8693b381c8acd05819e6be1a4122bdcf84fc99 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Mon, 23 Feb 2026 23:24:29 +0330 Subject: [PATCH 02/19] =?UTF-8?q?fix:=20rename=20wallet=20labels=20?= =?UTF-8?q?=E2=80=94=20=D8=A7=D8=B5=D9=84=DB=8C=D8=8C=20=D8=A7=D8=B9=D8=AA?= =?UTF-8?q?=D8=A8=D8=A7=D8=B1=DB=8C=D8=8C=20=D9=BE=D8=A7=D8=AF=D8=A7=D8=B4?= =?UTF-8?q?=20=D8=AA=DB=8C=D9=85=DB=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - عادی → اصلی (کیف پول اصلی) - شبکه → پاداش تیمی - پورسانت → پاداش تیمی - موجودی کیف پول → موجودی کیف پول اصلی Files: Wallet.razor, WalletService.cs, MagicWallet.razor, Profile/Index.razor, PaymentCallback.razor --- .../Pages/Profile/Index.razor | 2 +- .../Pages/Profile/MagicWallet.razor | 4 ++-- .../Pages/Profile/PaymentCallback.razor | 2 +- .../Pages/Profile/Wallet.razor | 20 +++++++++---------- .../Utilities/WalletService.cs | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor b/src/FrontOffice.Main/Pages/Profile/Index.razor index 2ca1a37..f01a2b8 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor @@ -45,7 +45,7 @@
- اعتباری + اصلی
@_walletCredit
diff --git a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor index 839d38a..1400932 100644 --- a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor +++ b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor @@ -22,7 +22,7 @@ کیف‌پول جادویی فعال نیست برای فعال شدن کیف‌پول جادویی، ابتدا پکیج پایه را خریداری کنید و سپس - تمام موجودی عادی خود را از فروشگاه خرج کنید. وقتی موجودی عادی به صفر برسد، + تمام موجودی اصلی خود را از فروشگاه خرج کنید. وقتی موجودی اصلی به صفر برسد، کیف‌پول جادویی فعال می‌شود. - در حالت جادویی، کمیسیون و پورسانت غیرفعال است + در حالت جادویی، کمیسیون و پاداش تیمی غیرفعال است بعد از اتمام سقف و خرج موجودی، با خرید مجدد پکیج دور جدید شروع می‌شود diff --git a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor index 952d7f1..974a033 100644 --- a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor +++ b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor @@ -31,7 +31,7 @@ - موجودی کیف پول: @FormatPrice(_walletBalance) + موجودی کیف پول اصلی: @FormatPrice(_walletBalance) موجودی کیف پول اعتباری: @FormatPrice(_discountBalance) diff --git a/src/FrontOffice.Main/Pages/Profile/Wallet.razor b/src/FrontOffice.Main/Pages/Profile/Wallet.razor index 8cdcbad..ce2af3a 100644 --- a/src/FrontOffice.Main/Pages/Profile/Wallet.razor +++ b/src/FrontOffice.Main/Pages/Profile/Wallet.razor @@ -9,13 +9,13 @@ - موجودی عادی + موجودی اصلی @_balances.Credit - موجودی شبکه + پاداش تیمی @_balances.Network @@ -82,8 +82,8 @@ تاریخ - عادی (تغییرات / مانده) - شبکه (تغییرات / مانده) + اصلی (تغییرات / مانده) + پاداش تیمی (تغییرات / مانده) اعتباری (تغییرات / مانده) شناسه ارجاع توضیحات @@ -92,7 +92,7 @@ @context.Date - + @(context.CreditChange != 0 ? (context.CreditChange > 0 ? "+" : "") + FormatPrice(context.CreditChange) : "-") @@ -100,7 +100,7 @@ @FormatPrice(context.CreditBalance) - + @(context.NetworkChange != 0 ? (context.NetworkChange > 0 ? "+" : "") + FormatPrice(context.NetworkChange) : "-") @@ -136,10 +136,10 @@ - + - عادی + اصلی @(tx.CreditChange != 0 ? (tx.CreditChange > 0 ? "+" : "") + FormatPrice(tx.CreditChange) : "-") @@ -147,10 +147,10 @@ - + - شبکه + پاداش تیمی @(tx.NetworkChange != 0 ? (tx.NetworkChange > 0 ? "+" : "") + FormatPrice(tx.NetworkChange) : "-") diff --git a/src/FrontOffice.Main/Utilities/WalletService.cs b/src/FrontOffice.Main/Utilities/WalletService.cs index 5b0bcfb..6ae3128 100644 --- a/src/FrontOffice.Main/Utilities/WalletService.cs +++ b/src/FrontOffice.Main/Utilities/WalletService.cs @@ -119,10 +119,10 @@ public class WalletService var parts = new List(); if (model.ChangeValue != 0) - parts.Add(model.ChangeValue > 0 ? "شارژ عادی" : "برداشت عادی"); + parts.Add(model.ChangeValue > 0 ? "شارژ اصلی" : "برداشت اصلی"); if (model.ChangeNerworkValue != 0) - parts.Add(model.ChangeNerworkValue > 0 ? "دریافت شبکه" : "برداشت شبکه"); + parts.Add(model.ChangeNerworkValue > 0 ? "دریافت پاداش تیمی" : "برداشت پاداش تیمی"); if (model.ChangeDiscountValue != 0) parts.Add(model.ChangeDiscountValue > 0 ? "شارژ اعتباری" : "خرید اعتباری"); From b82cac4af10a46827f4d406ee8ebe9d9f000f03b Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 02:01:54 +0330 Subject: [PATCH 03/19] =?UTF-8?q?Cleanup:=20Remove=20hardcoded=20'=D9=BE?= =?UTF-8?q?=DA=A9=DB=8C=D8=AC=20=D8=B7=D9=84=D8=A7=DB=8C=DB=8C'=20labels,?= =?UTF-8?q?=20add=20PackageTitle=20to=20DTO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MyPackages.razor: dynamic PackageTitle from DTO instead of hardcoded 'پکیج طلایی' - Packages.razor: generic text for purchase status and info section - PackageService.cs: add PackageTitle to UserPackageStatusDto record --- src/FrontOffice.Main/Pages/Package/MyPackages.razor | 6 +++--- src/FrontOffice.Main/Pages/Package/Packages.razor | 6 +++--- src/FrontOffice.Main/Utilities/PackageService.cs | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/FrontOffice.Main/Pages/Package/MyPackages.razor b/src/FrontOffice.Main/Pages/Package/MyPackages.razor index 7c137ed..f8ea30b 100644 --- a/src/FrontOffice.Main/Pages/Package/MyPackages.razor +++ b/src/FrontOffice.Main/Pages/Package/MyPackages.razor @@ -20,7 +20,7 @@ شما هنوز پکیجی خریداری نکرده‌اید - با خرید پکیج طلایی، به باشگاه مشتریان بپیوندید و از مزایای ویژه مانند پاداش هفتگی و تیم‌سازی بهره‌مند شوید. + با خرید پکیج، به باشگاه مشتریان بپیوندید و از مزایای ویژه مانند پاداش هفتگی و تیم‌سازی بهره‌مند شوید. نوع پکیج: - پکیج طلایی + @_userStatus.PackageTitle @@ -146,7 +146,7 @@ - مزایای پکیج طلایی + مزایای پکیج diff --git a/src/FrontOffice.Main/Pages/Package/Packages.razor b/src/FrontOffice.Main/Pages/Package/Packages.razor index c7fb39c..6463b59 100644 --- a/src/FrontOffice.Main/Pages/Package/Packages.razor +++ b/src/FrontOffice.Main/Pages/Package/Packages.razor @@ -27,7 +27,7 @@ { - شما قبلاً پکیج طلایی را خریداری کرده‌اید. + شما قبلاً پکیج را خریداری کرده‌اید. @if (_userStatus.IsClubMemberActive) { عضویت باشگاه شما فعال است. @@ -100,9 +100,9 @@ - چرا پکیج طلایی؟ + چرا پکیج بخریم؟ - با خرید پکیج طلایی، علاوه بر دسترسی به محصولات ویژه، می‌توانید تیم فروش خود را بسازید + با خرید پکیج، علاوه بر دسترسی به محصولات ویژه، می‌توانید تیم فروش خود را بسازید و از پاداش‌های هفتگی بهره‌مند شوید. هر چه تیم شما گسترده‌تر، درآمد شما بیشتر! diff --git a/src/FrontOffice.Main/Utilities/PackageService.cs b/src/FrontOffice.Main/Utilities/PackageService.cs index ba1df1c..d134663 100644 --- a/src/FrontOffice.Main/Utilities/PackageService.cs +++ b/src/FrontOffice.Main/Utilities/PackageService.cs @@ -20,6 +20,7 @@ public record PackageDto( /// public record UserPackageStatusDto( bool HasPurchasedPackage, + string? PackageTitle, string? PurchaseMethod, // DayaLoan, DirectPurchase, or null bool IsClubMemberActive, long WalletBalance, @@ -104,6 +105,7 @@ public class PackageService // TODO: Connect to GetUserPackageStatus RPC when available in FrontOffice.BFF return Task.FromResult(new UserPackageStatusDto( HasPurchasedPackage: false, + PackageTitle: null, PurchaseMethod: null, IsClubMemberActive: false, WalletBalance: 0, From 71f391ac8054021def8a321621b6ac2f55e8da56 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 02:35:59 +0330 Subject: [PATCH 04/19] Phase 7b: Migrate deprecated RPCs to Customer* RPCs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Profile/Index: InitiateBasePackagePayment → CustomerPurchasePackage - Fetches base package via GetCustomerPackages, passes PackageId + PurchaseMethod - Profile/PaymentCallback: VerifyBasePackagePayment → CustomerVerifyPackagePurchase - Uses Authority/Status (from ZarinPal callback) + orderId (from embedded URL param) - TransactionId no longer required (CMS finds it internally) --- .../Pages/Profile/Index.razor.cs | 25 ++++++++++++++----- .../Pages/Profile/PaymentCallback.razor | 19 ++++++++------ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs index f64e0d5..26a88c2 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs @@ -413,13 +413,24 @@ public partial class Index 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 + // Get available packages and pick the first (base) one + var packages = await PackageContract.GetCustomerPackagesAsync(new GetCustomerPackagesRequest()); + var basePackage = packages?.Models?.FirstOrDefault(); + if (basePackage == null) { - CallbackUrl = callbackUrl + Snackbar.Add("پکیجی برای خرید یافت نشد", Severity.Error); + return; + } + + // Call CMS to initiate purchase (UserId from JWT, PackageId explicit) + // First, get orderId — then embed in callback URL + var tempCallbackUrl = $"{Navigation.BaseUri}profile/payment-callback"; + + var response = await PackageContract.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest + { + PackageId = basePackage.Id, + CallbackUrl = tempCallbackUrl, + PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway }); if (!response.Success) @@ -429,6 +440,8 @@ public partial class Index } // Redirect to payment gateway + // ZarinPal appends ?Authority=...&Status=... to callback URL + // PaymentCallback uses Authority+Status to verify via CustomerVerifyPackagePurchase if (!string.IsNullOrEmpty(response.PaymentGatewayUrl)) { Navigation.NavigateTo(response.PaymentGatewayUrl, forceLoad: true); diff --git a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor index 974a033..7f2785d 100644 --- a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor +++ b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor @@ -101,7 +101,7 @@ { try { - if (OrderId <= 0 || TransactionId <= 0 || string.IsNullOrEmpty(Authority)) + if (OrderId <= 0 || string.IsNullOrEmpty(Authority)) { _isSuccess = false; _message = "پارامترهای پرداخت نامعتبر است"; @@ -110,20 +110,23 @@ return; } - var response = await PackageContractClient.VerifyBasePackagePaymentAsync(new VerifyBasePackagePaymentRequest + var response = await PackageContractClient.CustomerVerifyPackagePurchaseAsync(new CustomerVerifyPackagePurchaseRequest { OrderId = OrderId, - TransactionId = TransactionId, - PaymentSuccess = Status == "OK", - RefId = !string.IsNullOrEmpty(Authority) ? Authority : null, - Message = !string.IsNullOrEmpty(Status) ? Status : null + Authority = Authority ?? string.Empty, + Status = Status ?? string.Empty }); _isSuccess = response.Success; _message = response.Message; _refId = response.ReferenceCode ?? ""; - _walletBalance = response.WalletBalance; - _discountBalance = response.DiscountBalance; + + // Wallet balances are no longer in verify response — show from purchase info + if (response.PurchaseInfo != null) + { + _walletBalance = response.PurchaseInfo.AmountPaid; + _discountBalance = 0; // Available in wallet page + } // اگر پرداخت موفق بود، توکن را refresh می‌کنیم // چون PackagePurchaseMethod تغییر کرده و باید در claims جدید باشد From 0bbc11e82cf1fb5e55b81a45f817d9c15dd54a21 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 03:33:58 +0330 Subject: [PATCH 05/19] Phase 8a: Wire up Checkout page to Customer* RPCs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Checkout.razor.cs: Rewrite ProcessPayment() to use CustomerPurchasePackageAsync instead of disabled dead code (old TransactionsContract + UserOrderContract flow removed) - Callback URL now points to /profile/payment-callback (reuses existing verify page with CustomerVerifyPackagePurchase) - Remove unused DI: UserOrderContract, TransactionContract - Remove unused usings: Transactions, UserOrder, WellKnownTypes - Profile/Index.razor.cs: Rename basePackage → selectedPackage, tempCallbackUrl → callbackUrl (cosmetic) - Build: 0 errors --- src/FrontOffice.Main/Pages/Checkout.razor.cs | 50 +++++++------------ .../Pages/Profile/Index.razor.cs | 13 +++-- 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/src/FrontOffice.Main/Pages/Checkout.razor.cs b/src/FrontOffice.Main/Pages/Checkout.razor.cs index b132c8d..cdb5cb2 100644 --- a/src/FrontOffice.Main/Pages/Checkout.razor.cs +++ b/src/FrontOffice.Main/Pages/Checkout.razor.cs @@ -1,10 +1,7 @@ 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 MudBlazor; using Severity = MudBlazor.Severity; @@ -15,8 +12,6 @@ public partial class Checkout { [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; } @@ -173,9 +168,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 +178,29 @@ public partial class Checkout try { - // Step 1: Create payment request - var paymentRequest = new CustomerPaymentRequestRequest + var callbackUrl = $"{Navigation.BaseUri}profile/payment-callback"; + + 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 - }; + CallbackUrl = callbackUrl, + 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) { diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs index 26a88c2..06ee349 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs @@ -413,23 +413,22 @@ public partial class Index try { - // Get available packages and pick the first (base) one + // Get available packages and pick the first one var packages = await PackageContract.GetCustomerPackagesAsync(new GetCustomerPackagesRequest()); - var basePackage = packages?.Models?.FirstOrDefault(); - if (basePackage == null) + var selectedPackage = packages?.Models?.FirstOrDefault(); + if (selectedPackage == null) { Snackbar.Add("پکیجی برای خرید یافت نشد", Severity.Error); return; } // Call CMS to initiate purchase (UserId from JWT, PackageId explicit) - // First, get orderId — then embed in callback URL - var tempCallbackUrl = $"{Navigation.BaseUri}profile/payment-callback"; + var callbackUrl = $"{Navigation.BaseUri}profile/payment-callback"; var response = await PackageContract.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest { - PackageId = basePackage.Id, - CallbackUrl = tempCallbackUrl, + PackageId = selectedPackage.Id, + CallbackUrl = callbackUrl, PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway }); From d71d463668bdbcc45c73e643712e426afbb4b32a Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 17:52:37 +0330 Subject: [PATCH 06/19] =?UTF-8?q?Phase=208c:=20FO=20Package=20pages=20?= =?UTF-8?q?=E2=80=94=20Customer=20RPCs=20+=20dynamic=20features?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NuGet: 0.0.182 → 0.0.185 + local feed source - PackageDetail: GetPackageAsync → GetCustomerPackageDetailsAsync Features & specs now driven by API (PackageFeature) not hardcoded - Packages.razor: Un-excluded from build, dynamic feature bullets from CustomerPackageModel (DiscountMultiplier, MagicWallet, etc.) - PackageService: Enriched PackageDto with 8 new package fields GetUserPackageStatusAsync connected to real RPC (was stub) --- src/FrontOffice.Main/FrontOffice.Main.csproj | 8 +-- src/FrontOffice.Main/NuGet.config | 2 + .../Pages/Package/Packages.razor | 49 +++++++++++----- .../Pages/PackageDetail.razor.cs | 50 +++++++++-------- .../Utilities/PackageService.cs | 56 +++++++++++++++---- 5 files changed, 109 insertions(+), 56 deletions(-) diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj index cbd5f4e..8e37fc1 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/Package/Packages.razor b/src/FrontOffice.Main/Pages/Package/Packages.razor index 6463b59..5b5aee0 100644 --- a/src/FrontOffice.Main/Pages/Package/Packages.razor +++ b/src/FrontOffice.Main/Pages/Package/Packages.razor @@ -64,20 +64,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) + { + + + پکیج پایه + + } 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/Utilities/PackageService.cs b/src/FrontOffice.Main/Utilities/PackageService.cs index d134663..a326557 100644 --- a/src/FrontOffice.Main/Utilities/PackageService.cs +++ b/src/FrontOffice.Main/Utilities/PackageService.cs @@ -10,7 +10,15 @@ 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); } @@ -56,7 +64,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) @@ -100,16 +116,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, - PackageTitle: null, - 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); + } } } From 40882c8b8078ce61e88dbe33b490e217f7f927ba Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 18:00:47 +0330 Subject: [PATCH 07/19] =?UTF-8?q?Phase=208d:=20NuGet=20bump=200.0.185=20?= =?UTF-8?q?=E2=86=92=200.0.186=20(proto=20cleanup)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/FrontOffice.Main/FrontOffice.Main.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj index 8e37fc1..5c186a9 100644 --- a/src/FrontOffice.Main/FrontOffice.Main.csproj +++ b/src/FrontOffice.Main/FrontOffice.Main.csproj @@ -11,7 +11,7 @@ - + From a956cb90a1f0a6d96c4c9fdbbf2f5034bdc06f3a Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 21:08:30 +0330 Subject: [PATCH 08/19] feat(commission): add per-package filtering to commission pages - CommissionDtos: add PackageId + PackageTitle to CommissionPayoutDto and WeeklyBalanceDto - CommissionService: add packageId parameter to GetMyCommissionPayoutsAsync and GetMyWeeklyBalanceAsync - CommissionDashboardPage: add package filter dropdown + package column in desktop/mobile views - WeeklyBalancePage: add package filter dropdown + package chip in balance info section - Update NuGet Foursat.CMSMicroservice.Protobuf to v0.0.187 --- src/FrontOffice.Main/FrontOffice.Main.csproj | 2 +- .../Commission/CommissionDashboardPage.razor | 25 ++++++++++++++--- .../CommissionDashboardPage.razor.cs | 20 +++++++++++++- .../Pages/Commission/WeeklyBalancePage.razor | 26 +++++++++++------- .../Commission/WeeklyBalancePage.razor.cs | 19 ++++++++++++- .../Utilities/CommissionDtos.cs | 4 +++ .../Utilities/CommissionService.cs | 27 +++++++++++++++---- 7 files changed, 102 insertions(+), 21 deletions(-) diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj index 5c186a9..9f61d09 100644 --- a/src/FrontOffice.Main/FrontOffice.Main.csproj +++ b/src/FrontOffice.Main/FrontOffice.Main.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor b/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor index bd0dda9..555fc50 100644 --- a/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor +++ b/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor @@ -13,7 +13,7 @@ - + - + همه در انتظار @@ -34,7 +34,16 @@ لغو شده - + + + همه پکیج‌ها + @foreach (var pkg in _packages) + { + @pkg.Title + } + + + اعمال فیلتر @@ -87,6 +96,7 @@ هفته + پکیج امتیاز مبلغ (تومان) وضعیت @@ -97,6 +107,11 @@ @context.WeekDisplayName + + + @(string.IsNullOrEmpty(context.PackageTitle) ? "-" : context.PackageTitle) + + @context.BalancesEarned @context.AmountFormatted @@ -127,6 +142,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/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 From 3bffc13ffd700b9eb8f7ff29716ce175b5b30c6a Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 26 Feb 2026 22:01:55 +0330 Subject: [PATCH 09/19] feat(T4.2+T4.3+F3): conditional payment, re-purchase, PV display T4.2: Conditional Payment in Checkout - Load package flags via PackageService.GetAllPackagesAsync - Respect SupportsDirectPurchase/SupportsDayaPurchase flags - Add Daya loan payment button when supported - Show alert when no payment method available T4.3: MyPackages Re-Purchase Logic - Load MagicWallet status to detect cycle completion - Show re-purchase CTA when magic cycle is complete - Show magic wallet progress bar during active cycle - Display deposit progress, remaining, and credited amounts F3: PV Display in Order Details - Add CalculateOrderPVAsync to OrderService - Show per-product PV and total PV in Store OrderDetail - Update NuGet to v0.0.188 --- src/FrontOffice.Main/FrontOffice.Main.csproj | 2 +- src/FrontOffice.Main/Pages/Checkout.razor | 23 ++++++- src/FrontOffice.Main/Pages/Checkout.razor.cs | 33 +++++++--- .../Pages/Package/MyPackages.razor | 64 +++++++++++++++++++ .../Pages/Package/MyPackages.razor.cs | 21 ++++++ .../Pages/Store/OrderDetail.razor | 24 +++++++ .../Pages/Store/OrderDetail.razor.cs | 5 ++ .../Utilities/OrderService.cs | 18 ++++++ 8 files changed, 180 insertions(+), 10 deletions(-) diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj index 9f61d09..9be664d 100644 --- a/src/FrontOffice.Main/FrontOffice.Main.csproj +++ b/src/FrontOffice.Main/FrontOffice.Main.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/FrontOffice.Main/Pages/Checkout.razor b/src/FrontOffice.Main/Pages/Checkout.razor index 6662eb7..036390a 100644 --- a/src/FrontOffice.Main/Pages/Checkout.razor +++ b/src/FrontOffice.Main/Pages/Checkout.razor @@ -207,11 +207,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 cdb5cb2..770a4b8 100644 --- a/src/FrontOffice.Main/Pages/Checkout.razor.cs +++ b/src/FrontOffice.Main/Pages/Checkout.razor.cs @@ -3,6 +3,7 @@ using CMSMicroservice.Protobuf.Protos.Package; using CMSMicroservice.Protobuf.Protos.UserAddress; using FrontOffice.Main.Utilities; using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; using MudBlazor; using Severity = MudBlazor.Severity; @@ -10,6 +11,7 @@ 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!; @@ -47,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; } @@ -213,5 +218,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/Package/MyPackages.razor b/src/FrontOffice.Main/Pages/Package/MyPackages.razor index f8ea30b..8aba2d6 100644 --- a/src/FrontOffice.Main/Pages/Package/MyPackages.razor +++ b/src/FrontOffice.Main/Pages/Package/MyPackages.razor @@ -35,6 +35,70 @@ else { + + @* 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) + + + + + + } + 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/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/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 From 816dcb70d1dce3228d12751f2947e3cb94060684 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 02:41:24 +0330 Subject: [PATCH 10/19] =?UTF-8?q?fix:=20remove=20hardcoded=2056M=20amounts?= =?UTF-8?q?=20=E2=80=94=20dynamic=20package=20pricing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ActivationSection: load base package price via PackageService on init - ClubMembershipContractDialog: dynamic package name/price in contract text --- .../Components/ActivationSection.razor.cs | 18 ++++++++++++++--- .../Shared/ClubMembershipContractDialog.razor | 20 ++++++++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) 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/Shared/ClubMembershipContractDialog.razor b/src/FrontOffice.Main/Shared/ClubMembershipContractDialog.razor index 9fb7cf7..0315eb8 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 / 10:N0} تومان"; + } + } + catch { /* fallback to defaults */ } + } // این Modal غیرقابل بسته شدن است - Options در ShowAsync تنظیم می‌شوند @@ -242,7 +260,7 @@

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

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

-

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

+

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

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

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

From 474d364a2531ace1795d79143ceb488e50ebc88d Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 05:40:04 +0330 Subject: [PATCH 11/19] feat(Q28): UI Guidance alerts across FrontOffice (G1-G7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Q28: Added informational/warning alerts to 7 FO pages: G1: Packages — package system explanation G2: Checkout — package benefits summary G3: MyPackages — cycle completion guidance G4: MagicWallet — per-package settings warning G5: Commission — independent pool explanation G6: Membership — single-sign contract info G7: ActivationSection — estimated cost note --- src/FrontOffice.Main/Pages/Checkout.razor | 5 +++++ .../Pages/Club/Components/ActivationSection.razor | 7 ++++++- src/FrontOffice.Main/Pages/Club/MembershipPage.razor | 5 +++++ .../Pages/Commission/CommissionDashboardPage.razor | 5 +++++ src/FrontOffice.Main/Pages/Package/MyPackages.razor | 5 +++++ src/FrontOffice.Main/Pages/Package/Packages.razor | 5 +++++ src/FrontOffice.Main/Pages/Profile/MagicWallet.razor | 5 +++++ 7 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/FrontOffice.Main/Pages/Checkout.razor b/src/FrontOffice.Main/Pages/Checkout.razor index 036390a..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) { 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/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 555fc50..a590a8f 100644 --- a/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor +++ b/src/FrontOffice.Main/Pages/Commission/CommissionDashboardPage.razor @@ -10,6 +10,11 @@ + @* ── G5: توضیح محاسبه پاداش (Q28) ── *@ + + پاداش هفتگی بر اساس هر پکیج جداگانه محاسبه می‌شود. هر پکیج Pool پورسانت مستقل دارد و Carryover بر اساس پکیج زیرمجموعه‌هاست. + + diff --git a/src/FrontOffice.Main/Pages/Package/MyPackages.razor b/src/FrontOffice.Main/Pages/Package/MyPackages.razor index 8aba2d6..cefeb60 100644 --- a/src/FrontOffice.Main/Pages/Package/MyPackages.razor +++ b/src/FrontOffice.Main/Pages/Package/MyPackages.razor @@ -34,6 +34,11 @@ } else { + @* ── G3: راهنمای پکیج‌های من (Q28) ── *@ + + بعد از تکمیل چرخه کیف‌پول جادویی، می‌توانید مجدداً پکیج خریداری کرده و دور جدیدی را آغاز کنید. + + @* Re-Purchase / Cycle Progress Section *@ diff --git a/src/FrontOffice.Main/Pages/Package/Packages.razor b/src/FrontOffice.Main/Pages/Package/Packages.razor index 5b5aee0..3f769b6 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) { diff --git a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor index 1400932..5bc59b7 100644 --- a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor +++ b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor @@ -36,6 +36,11 @@ { @* ═══ حالت جادویی فعال ═══ *@ + @* ── G4: هشدار تنظیمات پکیج‌بیس (Q28) ── *@ + + سقف واریز و ضریب اعتبار کیف‌پول جادویی بر اساس پکیج شما تنظیم شده است. پکیج‌های بالاتر سقف و ضریب بیشتری دارند. + + @* ─── وضعیت و پیشرفت ─── *@ From ecc4f447ba187270a187d3d0330f68be6a0c9c4b Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 09:01:12 +0330 Subject: [PATCH 12/19] =?UTF-8?q?F6+F7:=20=D9=85=D9=82=D8=A7=D8=AF=DB=8C?= =?UTF-8?q?=D8=B1=20=D8=AF=D8=A7=DB=8C=D9=86=D8=A7=D9=85=DB=8C=DA=A9=20?= =?UTF-8?q?=DA=A9=DB=8C=D9=81=E2=80=8C=D9=BE=D9=88=D9=84=20=D8=AC=D8=A7?= =?UTF-8?q?=D8=AF=D9=88=DB=8C=DB=8C=D8=8C=20=D8=A7=D8=B5=D9=84=D8=A7=D8=AD?= =?UTF-8?q?=20rename=D8=8C=20=D8=A7=D9=81=D8=B2=D8=A7=DB=8C=D8=B4=20=D8=B3?= =?UTF-8?q?=D9=82=D9=81=20=D8=AA=D8=AE=D9=81=DB=8C=D9=81=DB=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F6: MagicWallet.razor — جایگزینی ×2.5 و سقف‌های ثابت با مقادیر از API - WalletService: MagicMultiplier + MagicMaxCredit از gRPC response - Index.razor.cs: 'شارژ ×2.5' → 'شارژ چند‌برابری' (تایل داشبورد) F7: ChargeDiscountWallet.razor — Max از 100M تومان به 1B تومان اصلاح rename: - ConfigureServices: UserWalletChangeLog → UserWalletHistory - WalletService: اصلاح تمام رفرنس‌ها NuGet: v0.0.189 --- src/FrontOffice.Main/ConfigureServices.cs | 4 ++-- src/FrontOffice.Main/FrontOffice.Main.csproj | 2 +- .../Pages/Profile/ChargeDiscountWallet.razor | 4 ++-- .../Pages/Profile/Index.razor.cs | 2 +- .../Pages/Profile/MagicWallet.razor | 14 +++++++------- src/FrontOffice.Main/Utilities/WalletService.cs | 17 ++++++++++------- 6 files changed, 23 insertions(+), 20 deletions(-) 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 9be664d..c37f88b 100644 --- a/src/FrontOffice.Main/FrontOffice.Main.csproj +++ b/src/FrontOffice.Main/FrontOffice.Main.csproj @@ -11,7 +11,7 @@ - + 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/Index.razor.cs b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs index 06ee349..727ebcc 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs @@ -55,7 +55,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;"), diff --git a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor index 5bc59b7..b9e3b35 100644 --- a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor +++ b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor @@ -97,8 +97,8 @@
- مبلغ واریزی شما ×۲.۵ به موجودی اضافه می‌شود. - مثلاً ۱۰ میلیون واریز = ۲۵ میلیون اعتبار. + مبلغ واریزی شما ×@_status.MagicMultiplier.ToString("0.#") به موجودی اضافه می‌شود. + مثلاً ۱۰ میلیون واریز = @FormatToman((long)(10_000_000 * (decimal)_status.MagicMultiplier)) اعتبار. @@ -120,8 +120,8 @@ @FormatToman(_chargeAmount) - اعتبار دریافتی (×۲.۵): - @FormatToman((long)(_chargeAmount * 2.5m)) + اعتبار دریافتی (×@_status.MagicMultiplier.ToString("0.#")): + @FormatToman((long)(_chargeAmount * (decimal)_status.MagicMultiplier)) } @@ -176,13 +176,13 @@ - هر مبلغ واریزی ×۲.۵ به موجودی اضافه می‌شود + هر مبلغ واریزی ×@_status.MagicMultiplier.ToString("0.#") به موجودی اضافه می‌شود - سقف واریز در هر دور: ۱۰۰ میلیون تومان + سقف واریز در هر دور: @FormatPrice(_status.MagicMaxDeposit) - حداکثر اعتبار: ۲۵۰ میلیون تومان (سود ۱۵۰٪) + حداکثر اعتبار: @FormatPrice(_status.MagicMaxCredit) در حالت جادویی، کمیسیون و پاداش تیمی غیرفعال است diff --git a/src/FrontOffice.Main/Utilities/WalletService.cs b/src/FrontOffice.Main/Utilities/WalletService.cs index 6ae3128..5833b33 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 From a3681a8bcdca1410dbfcf089b8cab06e2b9a44cc Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 20:14:40 +0330 Subject: [PATCH 13/19] =?UTF-8?q?feat:=20=D8=AF=D8=A7=D8=B4=D8=A8=D9=88?= =?UTF-8?q?=D8=B1=D8=AF=20=E2=80=94=20=D9=84=DB=8C=D8=B3=D8=AA=20=DA=A9?= =?UTF-8?q?=D8=A7=D8=B4=DB=8C=20=D9=BE=DA=A9=DB=8C=D8=AC=E2=80=8C=D9=87?= =?UTF-8?q?=D8=A7=20+=20=D9=85=D8=AF=D8=A7=D9=84=20=D8=A7=D9=86=D8=AA?= =?UTF-8?q?=D8=AE=D8=A7=D8=A8=20=D8=B1=D9=88=D8=B4=20=D9=BE=D8=B1=D8=AF?= =?UTF-8?q?=D8=A7=D8=AE=D8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - حذف متن hardcoded 'پکیج پایه ۵۶ میلیون تومان' و دیالوگ inline - ایجاد PackagePurchaseDialog — کامپوننت مستقل 2 مرحله‌ای: مرحله ۱: نمایش کاشی‌های پکیج (ریسپانسیو ۲-۳ تایی) مرحله ۲: انتخاب روش پرداخت (درگاه بانکی / دایا الماس) - دایا الماس فقط برای پکیج پایه + دور اول نمایش داده میشه - پکیج‌ها داینامیک از API بارگذاری میشن - تغییر متن CTA از 'شروع فرآیند تامین اعتبار' به 'تهیه پکیج' - تغییر متن راهنما از 'پکیج پایه را تهیه کنید' به 'یکی از پکیج‌ها را تهیه کنید' - CSS: hover effects + badge + responsive tiles --- .../Pages/Profile/Index.razor | 88 +------ .../Pages/Profile/Index.razor.cs | 92 +++---- .../Shared/PackagePurchaseDialog.razor | 234 ++++++++++++++++++ src/FrontOffice.Main/wwwroot/css/site.css | 27 ++ 4 files changed, 311 insertions(+), 130 deletions(-) create mode 100644 src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor 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 727ebcc..49820af 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 مجاز @@ -394,53 +393,65 @@ 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 { - // Get available packages and pick the first one - var packages = await PackageContract.GetCustomerPackagesAsync(new GetCustomerPackagesRequest()); - var selectedPackage = packages?.Models?.FirstOrDefault(); - if (selectedPackage == null) - { - Snackbar.Add("پکیجی برای خرید یافت نشد", Severity.Error); - return; - } - - // Call CMS to initiate purchase (UserId from JWT, PackageId explicit) var callbackUrl = $"{Navigation.BaseUri}profile/payment-callback"; - + var response = await PackageContract.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest { - PackageId = selectedPackage.Id, + PackageId = packageId, CallbackUrl = callbackUrl, PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway }); - + if (!response.Success) { Snackbar.Add(response.Message ?? "خطا در آغاز فرآیند پرداخت", Severity.Error); return; } - - // Redirect to payment gateway - // ZarinPal appends ?Authority=...&Status=... to callback URL - // PaymentCallback uses Authority+Status to verify via CustomerVerifyPackagePurchase + if (!string.IsNullOrEmpty(response.PaymentGatewayUrl)) { Navigation.NavigateTo(response.PaymentGatewayUrl, forceLoad: true); @@ -461,41 +472,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/Shared/PackagePurchaseDialog.razor b/src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor new file mode 100644 index 0000000..adaa7d6 --- /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)) + { + + @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/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; } +} From 3c1a8ffd5338002493c24659fb804839bc81c214 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 20:44:18 +0330 Subject: [PATCH 14/19] fix: 4 UI fixes for package display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Toman/Rial mismatch — Price from server is in Rial, now divides by 10 for correct Toman display 2. ضریب تخفیف → ضریب اعتبار label change in dialog + packages page 3. Back button already functional (ArrowForward in TitleContent) — verified 4. HTML rendering in package description via MarkupString --- src/FrontOffice.Main/Pages/Package/Packages.razor | 2 +- src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor | 4 ++-- src/FrontOffice.Main/Utilities/PackageService.cs | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/FrontOffice.Main/Pages/Package/Packages.razor b/src/FrontOffice.Main/Pages/Package/Packages.razor index 3f769b6..5281dfa 100644 --- a/src/FrontOffice.Main/Pages/Package/Packages.razor +++ b/src/FrontOffice.Main/Pages/Package/Packages.razor @@ -89,7 +89,7 @@ { - ضریب تخفیف: @package.DiscountMultiplier.ToString("F1")x + ضریب اعتبار: @package.DiscountMultiplier.ToString("F1")x } @if (package.MagicWalletMultiplier > 0) diff --git a/src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor b/src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor index adaa7d6..c997ff1 100644 --- a/src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor +++ b/src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor @@ -130,7 +130,7 @@ { - @pkg.Description + @((MarkupString)pkg.Description) } @* Key features *@ @@ -146,7 +146,7 @@ { - ضریب تخفیف: @pkg.DiscountMultiplier.ToString("F1")x + ضریب اعتبار: @pkg.DiscountMultiplier.ToString("F1")x } @if (pkg.SupportsDayaPurchase) diff --git a/src/FrontOffice.Main/Utilities/PackageService.cs b/src/FrontOffice.Main/Utilities/PackageService.cs index a326557..f1e9bab 100644 --- a/src/FrontOffice.Main/Utilities/PackageService.cs +++ b/src/FrontOffice.Main/Utilities/PackageService.cs @@ -20,7 +20,8 @@ public record PackageDto( bool SupportsDayaPurchase = false, bool SupportsDirectPurchase = false) { - public string FormattedPrice => string.Format("{0:N0} تومان", Price); + /// قیمت به تومان — مقدار Price از سرور به ریال است + public string FormattedPrice => string.Format("{0:N0} تومان", Price / 10); } /// From 71b7e40994dc8947ad5220a0646aa152078c4d40 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 21:02:54 +0330 Subject: [PATCH 15/19] =?UTF-8?q?fix:=20revert=20Price/10=20=E2=80=94=20DB?= =?UTF-8?q?=20stores=20Toman,=20display=20directly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous fix incorrectly divided by 10 assuming DB was Rial. DB values are in Toman, so FormattedPrice shows Price directly. --- src/FrontOffice.Main/Utilities/PackageService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FrontOffice.Main/Utilities/PackageService.cs b/src/FrontOffice.Main/Utilities/PackageService.cs index f1e9bab..f68850d 100644 --- a/src/FrontOffice.Main/Utilities/PackageService.cs +++ b/src/FrontOffice.Main/Utilities/PackageService.cs @@ -20,8 +20,8 @@ public record PackageDto( bool SupportsDayaPurchase = false, bool SupportsDirectPurchase = false) { - /// قیمت به تومان — مقدار Price از سرور به ریال است - public string FormattedPrice => string.Format("{0:N0} تومان", Price / 10); + /// قیمت فرمت‌شده به تومان + public string FormattedPrice => string.Format("{0:N0} تومان", Price); } /// From 5ded91a58a0d9eaf773979a0d615bee97839e014 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 21:31:45 +0330 Subject: [PATCH 16/19] =?UTF-8?q?fix:=20payment=20success=20page=20?= =?UTF-8?q?=E2=80=94=20show=20TransactionId,=20real=20wallet=20balance,=20?= =?UTF-8?q?fix=20back=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - کد ارجاعی: از TransactionId جدول تراکنش‌ها بجای RefId زرین‌پال - موجودی: از WalletService.GetBalancesAsync() موجودی واقعی کیف پول اصلی - حذف نمایش موجودی کیف پول اعتباری (نیازی نیست) - دکمه بازگشت: Href=/profile بجای history.back() (که به درگاه می‌رفت) --- .../Pages/Profile/PaymentCallback.razor | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor index 7f2785d..20edafd 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,24 +21,21 @@ پرداخت موفق @_message - @if (!string.IsNullOrEmpty(_refId)) + @if (_transactionId > 0) { - کد پیگیری: @_refId + کد ارجاعی: @_transactionId } - موجودی کیف پول اصلی: @FormatPrice(_walletBalance) - - - موجودی کیف پول اعتباری: @FormatPrice(_discountBalance) + موجودی کیف پول: @FormatPrice(_walletBalance) + Href="/profile"> بازگشت به پروفایل } @@ -49,7 +46,7 @@ @_message + Href="/profile"> بازگشت به پروفایل @@ -85,9 +82,8 @@ private bool _isLoading = true; private bool _isSuccess; private string _message = string.Empty; - private string? _refId; + private long _transactionId; private long _walletBalance; - private long _discountBalance; protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -119,19 +115,18 @@ _isSuccess = response.Success; _message = response.Message; - _refId = response.ReferenceCode ?? ""; + _transactionId = response.TransactionId; - // Wallet balances are no longer in verify response — show from purchase info - if (response.PurchaseInfo != null) - { - _walletBalance = response.PurchaseInfo.AmountPaid; - _discountBalance = 0; // Available in wallet page - } - - // اگر پرداخت موفق بود، توکن را refresh می‌کنیم - // چون PackagePurchaseMethod تغییر کرده و باید در claims جدید باشد + // اگر پرداخت موفق بود، موجودی واقعی کیف پول رو بگیر و توکن رو refresh کن if (_isSuccess) { + try + { + var balances = await WalletService.GetBalancesAsync(); + _walletBalance = balances.CreditBalance; + } + catch { /* اگر خطا شد، صفر نمایش بده */ } + await RefreshTokenAsync(); } } @@ -149,7 +144,7 @@ private void RetryPayment() { - NavManager.NavigateTo("/profile"); + NavManager.NavigateTo("/profile", forceLoad: true); } /// @@ -177,6 +172,4 @@ } private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); - - private async Task GoBack() => await JS.InvokeVoidAsync("history.back"); } From 2f9ef158598482c61f15e73d712b69ea4312dc5e Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 21:53:01 +0330 Subject: [PATCH 17/19] =?UTF-8?q?fix:=20remove=20double=20=C3=9710=20conve?= =?UTF-8?q?rsion=20in=20wallet=20charge=20=E2=80=94=20FO=20should=20send?= =?UTF-8?q?=20Toman,=20not=20Rial?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: FrontOffice was converting Toman→Rial (×10) before sending to CMS, but CMS ZarinPalService already does Toman→Rial conversion internally. This caused double ×10 = amounts shown 10x higher in payment gateway. Fixes: - MagicWallet.razor.cs: remove ×10 conversion, send _chargeAmount (Toman) directly - MagicWallet.razor: fix Max field (no /10), fix preset filter (no ×10) - MagicWallet.razor.cs: fix FormatPrice — values from CMS are Toman, not Rial - ChargeDiscountWallet.razor.cs: same fix — send Toman directly - ClubMembershipContractDialog.razor: fix Price display (no /10) --- .../Pages/Profile/ChargeDiscountWallet.razor.cs | 6 ++---- src/FrontOffice.Main/Pages/Profile/MagicWallet.razor | 4 ++-- .../Pages/Profile/MagicWallet.razor.cs | 10 ++++------ .../Shared/ClubMembershipContractDialog.razor | 2 +- 4 files changed, 9 insertions(+), 13 deletions(-) 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/MagicWallet.razor b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor index b9e3b35..f2301e4 100644 --- a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor +++ b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor @@ -106,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" @@ -130,7 +130,7 @@ @foreach (var preset in _presetAmounts) { - if (preset * 10 <= _status.MagicRemainingDeposit) + if (preset <= _status.MagicRemainingDeposit) { 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/Shared/ClubMembershipContractDialog.razor b/src/FrontOffice.Main/Shared/ClubMembershipContractDialog.razor index 0315eb8..18f3ed9 100644 --- a/src/FrontOffice.Main/Shared/ClubMembershipContractDialog.razor +++ b/src/FrontOffice.Main/Shared/ClubMembershipContractDialog.razor @@ -136,7 +136,7 @@ if (basePackage != null) { _packageDisplayName = basePackage.Title; - _packageDisplayPrice = $"{basePackage.Price / 10:N0} تومان"; + _packageDisplayPrice = $"{basePackage.Price:N0} تومان"; } } catch { /* fallback to defaults */ } From 2b1dc47dc743640c769558f44c7d911cf0050c3d Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 27 Feb 2026 22:21:02 +0330 Subject: [PATCH 18/19] =?UTF-8?q?cleanup:=20remove=20CallbackUrl=20from=20?= =?UTF-8?q?package=20purchase=20requests=20=E2=80=94=20CMS=20reads=20from?= =?UTF-8?q?=20config=20now?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/FrontOffice.Main/Pages/Checkout.razor.cs | 3 --- src/FrontOffice.Main/Pages/Profile/Index.razor.cs | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/FrontOffice.Main/Pages/Checkout.razor.cs b/src/FrontOffice.Main/Pages/Checkout.razor.cs index 770a4b8..253feef 100644 --- a/src/FrontOffice.Main/Pages/Checkout.razor.cs +++ b/src/FrontOffice.Main/Pages/Checkout.razor.cs @@ -183,12 +183,9 @@ public partial class Checkout try { - var callbackUrl = $"{Navigation.BaseUri}profile/payment-callback"; - var response = await PackageClient.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest { PackageId = _selectedPackage.Id, - CallbackUrl = callbackUrl, PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway }); diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs index 49820af..f9206ad 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs @@ -437,12 +437,9 @@ public partial class Index try { - var callbackUrl = $"{Navigation.BaseUri}profile/payment-callback"; - var response = await PackageContract.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest { PackageId = packageId, - CallbackUrl = callbackUrl, PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway }); From ff3696cda36c1ecf2fd6d44139123cd8cdd78742 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sat, 28 Feb 2026 03:27:58 +0330 Subject: [PATCH 19/19] feat: implement payment verification methods for magic wallet and discount wallet --- src/FrontOffice.Main/FrontOffice.Main.csproj | 2 +- .../Pages/Profile/PaymentCallback.razor | 213 +++++++++++++++--- .../Utilities/DiscountOrderService.cs | 21 ++ .../Utilities/WalletService.cs | 38 ++++ 4 files changed, 239 insertions(+), 35 deletions(-) diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj index c37f88b..61b82ff 100644 --- a/src/FrontOffice.Main/FrontOffice.Main.csproj +++ b/src/FrontOffice.Main/FrontOffice.Main.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor index 20edafd..d26bd56 100644 --- a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor +++ b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor @@ -35,8 +35,8 @@ - بازگشت به پروفایل + Href="@_successReturnUrl"> + @_successReturnText } else @@ -46,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; } @@ -84,6 +91,12 @@ private string _message = string.Empty; private long _transactionId; private long _walletBalance; + + // دکمه‌های بازگشت بسته به نوع پرداخت + private string _successReturnUrl = "/profile"; + private string _successReturnText = "بازگشت به پروفایل"; + private string _failReturnUrl = "/profile"; + private string _failReturnText = "بازگشت به پروفایل"; protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -97,37 +110,23 @@ { try { - if (OrderId <= 0 || string.IsNullOrEmpty(Authority)) - { - _isSuccess = false; - _message = "پارامترهای پرداخت نامعتبر است"; - _isLoading = false; - StateHasChanged(); - 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; + // تعیین نوع پرداخت — اگر type نباشد، پیش‌فرض خرید پکیج + var type = PaymentType?.ToLowerInvariant() ?? "package"; - // اگر پرداخت موفق بود، موجودی واقعی کیف پول رو بگیر و توکن رو refresh کن - if (_isSuccess) + switch (type) { - try - { - var balances = await WalletService.GetBalancesAsync(); - _walletBalance = balances.CreditBalance; - } - catch { /* اگر خطا شد، صفر نمایش بده */ } - - 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) @@ -141,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", forceLoad: true); + 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); } /// 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/WalletService.cs b/src/FrontOffice.Main/Utilities/WalletService.cs index 5833b33..7d6648b 100644 --- a/src/FrontOffice.Main/Utilities/WalletService.cs +++ b/src/FrontOffice.Main/Utilities/WalletService.cs @@ -266,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