From d44e99f4ab909c3743c2575683c128aacb4b13c4 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Fri, 26 Jun 2026 18:17:19 +0330 Subject: [PATCH] feat(profile): enhance ChargeCreditWallet with return URL handling and continue shopping button - Added support for return URL handling in ChargeCreditWallet to improve user experience after payment. - Introduced a "Continue Shopping" button that allows users to navigate back to their previous shopping session after successfully charging their wallet. - Updated the component to manage pending return URLs and ensure they are saved and retrieved correctly. These changes enhance the functionality of the ChargeCreditWallet page, providing a smoother transition for users post-transaction. --- .../Pages/Profile/ChargeCreditWallet.razor | 11 +++- .../Pages/Profile/ChargeCreditWallet.razor.cs | 42 +++++++++++++ .../Pages/Store/CheckoutSummary.razor.cs | 60 +++++++++++++++--- .../Shared/InsufficientCreditDialog.razor | 61 +++++++++++++++++++ .../Utilities/CreditChargeNavigation.cs | 50 +++++++++++++++ 5 files changed, 213 insertions(+), 11 deletions(-) create mode 100644 src/FrontOffice.Main/Shared/InsufficientCreditDialog.razor create mode 100644 src/FrontOffice.Main/Utilities/CreditChargeNavigation.cs diff --git a/src/FrontOffice.Main/Pages/Profile/ChargeCreditWallet.razor b/src/FrontOffice.Main/Pages/Profile/ChargeCreditWallet.razor index 6d30f81..611ecab 100644 --- a/src/FrontOffice.Main/Pages/Profile/ChargeCreditWallet.razor +++ b/src/FrontOffice.Main/Pages/Profile/ChargeCreditWallet.razor @@ -13,7 +13,16 @@ Class="rounded-lg" Variant="Variant.Filled"> @if (_paymentResult == "success") { - شارژ کیف پول اصلی با موفقیت انجام شد! ✅ + + شارژ کیف پول اصلی با موفقیت انجام شد! ✅ + + ادامه خرید + + } else if (_paymentResult == "cancelled") { diff --git a/src/FrontOffice.Main/Pages/Profile/ChargeCreditWallet.razor.cs b/src/FrontOffice.Main/Pages/Profile/ChargeCreditWallet.razor.cs index 7ab98cb..0cd1561 100644 --- a/src/FrontOffice.Main/Pages/Profile/ChargeCreditWallet.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/ChargeCreditWallet.razor.cs @@ -1,5 +1,6 @@ using FrontOffice.Main.Utilities; using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; using MudBlazor; namespace FrontOffice.Main.Pages.Profile; @@ -9,15 +10,38 @@ public partial class ChargeCreditWallet : ComponentBase private bool _isProcessing; private long _chargeAmount; private string? _paymentResult; + private string? _pendingReturnUrl; private readonly long[] _presetAmounts = { 500_000, 1_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000 }; [SupplyParameterFromQuery(Name = "payment")] public string? PaymentQueryParam { get; set; } + [SupplyParameterFromQuery(Name = "amount")] + public long? AmountQueryParam { get; set; } + + [SupplyParameterFromQuery(Name = "returnUrl")] + public string? ReturnUrlQueryParam { get; set; } + protected override void OnInitialized() { _paymentResult = PaymentQueryParam; + + if (AmountQueryParam is > 0) + _chargeAmount = CreditChargeNavigation.NormalizeChargeAmount(AmountQueryParam.Value); + + if (CreditChargeNavigation.IsValidReturnUrl(ReturnUrlQueryParam)) + _pendingReturnUrl = ReturnUrlQueryParam; + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender && !string.IsNullOrEmpty(_pendingReturnUrl)) + { + await CreditChargeNavigation.SaveReturnUrlAsync(jsRuntime, _pendingReturnUrl); + } + + await base.OnAfterRenderAsync(firstRender); } private async Task StartCreditCharge() @@ -29,6 +53,15 @@ public partial class ChargeCreditWallet : ComponentBase try { + if (!string.IsNullOrEmpty(_pendingReturnUrl)) + await CreditChargeNavigation.SaveReturnUrlAsync(jsRuntime, _pendingReturnUrl); + else + { + var storedReturnUrl = await CreditChargeNavigation.GetReturnUrlAsync(jsRuntime); + if (CreditChargeNavigation.IsValidReturnUrl(storedReturnUrl)) + _pendingReturnUrl = storedReturnUrl; + } + var (success, gatewayUrl, error) = await WalletService.InitiateCreditChargeAsync(_chargeAmount); if (success && !string.IsNullOrEmpty(gatewayUrl)) @@ -51,6 +84,15 @@ public partial class ChargeCreditWallet : ComponentBase } } + private async Task ContinueShoppingAsync() + { + var returnUrl = await CreditChargeNavigation.GetReturnUrlAsync(jsRuntime); + await CreditChargeNavigation.ClearReturnUrlAsync(jsRuntime); + + if (CreditChargeNavigation.IsValidReturnUrl(returnUrl)) + Navigation.NavigateTo(returnUrl!); + } + private static string FormatToman(long toman) => string.Format("{0:N0} تومان", toman); } diff --git a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs index 6a50925..3841807 100644 --- a/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs +++ b/src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs @@ -1,5 +1,6 @@ using CMSMicroservice.Protobuf.Protos.UserAddress; using CMSMicroservice.Protobuf.Protos.UserOrder; +using FrontOffice.Main.Shared; using FrontOffice.Main.Utilities; using Microsoft.AspNetCore.Components; using MudBlazor; @@ -33,7 +34,6 @@ public partial class CheckoutSummary : ComponentBase { await AuthDialogService.ShowAuthDialogAsync(); } - // لود سبد خرید (فقط اگر کاربر لاگین کرده باشد) await Cart.EnsureInitializedAsync(); await LoadAddresses(); await LoadWalletBalance(); @@ -43,7 +43,6 @@ public partial class CheckoutSummary : ComponentBase { if (firstRender) { - // بارگذاری نرخ VAT await VAT.LoadAsync(); } await base.OnAfterRenderAsync(firstRender); @@ -52,9 +51,7 @@ public partial class CheckoutSummary : ComponentBase private async Task LoadWalletBalance() { var walletResult = await WalletService.GetBalancesAsync(); - walletBalance = walletResult.CreditBalance - // + walletResult.NetworkBalance - ; + walletBalance = walletResult.CreditBalance; } private async Task LoadAddresses() @@ -93,11 +90,15 @@ public partial class CheckoutSummary : ComponentBase return; } + var totalRequired = VAT.AddVAT(Cart.Total); + if (await TryHandleInsufficientBalanceAsync(totalRequired)) + return; + try { var request = new SubmitShopBuyOrderRequest { - TotalAmount = VAT.AddVAT(Cart.Total) + TotalAmount = totalRequired }; var response = await UserOrderContract.SubmitShopBuyOrderAsync(request); @@ -107,17 +108,56 @@ public partial class CheckoutSummary : ComponentBase } catch (Exception ex) { + if (CreditChargeNavigation.IsInsufficientWalletBalance(ex)) + { + await LoadWalletBalance(); + await TryHandleInsufficientBalanceAsync(totalRequired); + return; + } + Snackbar.Add($"خطا در ثبت سفارش: {ex.Message}", Severity.Error); } } + private async Task TryHandleInsufficientBalanceAsync(long totalRequired) + { + if (walletBalance >= totalRequired) + return false; + + var shortfall = totalRequired - walletBalance; + var parameters = new DialogParameters + { + { x => x.CurrentBalance, walletBalance }, + { x => x.RequiredAmount, totalRequired }, + { x => x.ShortfallAmount, shortfall } + }; + + var dialog = await DialogService.ShowAsync( + "موجودی کافی نیست", + parameters, + new DialogOptions + { + CloseOnEscapeKey = true, + MaxWidth = MaxWidth.Small, + FullWidth = true + }); + + var result = await dialog.Result; + if (result is { Canceled: false, Data: long chargeShortfall }) + { + var chargeAmount = CreditChargeNavigation.NormalizeChargeAmount(chargeShortfall); + Navigation.NavigateTo(CreditChargeNavigation.BuildChargeUrl( + chargeAmount, + RouteConstants.Store.CheckoutSummary)); + } + + return true; + } + private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); - /// - /// محاسبه مالیات بر ارزش افزوده - /// private long CalculateVAT() => VAT.CalculateVAT(Cart.Total); private static string GetProductImageUrl(string? imageUrl) => string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/'); -} \ No newline at end of file +} diff --git a/src/FrontOffice.Main/Shared/InsufficientCreditDialog.razor b/src/FrontOffice.Main/Shared/InsufficientCreditDialog.razor new file mode 100644 index 0000000..da14999 --- /dev/null +++ b/src/FrontOffice.Main/Shared/InsufficientCreditDialog.razor @@ -0,0 +1,61 @@ + + + + + موجودی کیف پول اصلی کافی نیست + + + + + + برای تکمیل این سفارش، موجودی اصلی شما کافی نیست. می‌توانید کیف پول را شارژ کنید و سپس سفارش را ثبت کنید. + + + + + موجودی فعلی: + @FormatPrice(CurrentBalance) + + + مبلغ سفارش: + @FormatPrice(RequiredAmount) + + + + کمبود: + @FormatPrice(ShortfallAmount) + + + + + + + بستن + + شارژ حساب اصلی + + + + +@code { + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public long CurrentBalance { get; set; } + + [Parameter] + public long RequiredAmount { get; set; } + + [Parameter] + public long ShortfallAmount { get; set; } + + private void Cancel() => MudDialog.Cancel(); + + private void GoToCharge() => MudDialog.Close(DialogResult.Ok(ShortfallAmount)); + + private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price); +} diff --git a/src/FrontOffice.Main/Utilities/CreditChargeNavigation.cs b/src/FrontOffice.Main/Utilities/CreditChargeNavigation.cs new file mode 100644 index 0000000..0cc0bbf --- /dev/null +++ b/src/FrontOffice.Main/Utilities/CreditChargeNavigation.cs @@ -0,0 +1,50 @@ +using Grpc.Core; +using Microsoft.JSInterop; + +namespace FrontOffice.Main.Utilities; + +public static class CreditChargeNavigation +{ + public const long MinChargeAmount = 10_000; + public const string ReturnUrlStorageKey = "credit_charge_return_url"; + private const string InsufficientBalanceMarker = "موجودی کیف پول کافی نیست"; + + public static long NormalizeChargeAmount(long shortfall) => + Math.Max(shortfall, MinChargeAmount); + + public static string BuildChargeUrl(long amount, string? returnUrl) + { + var url = $"{RouteConstants.Profile.ChargeCreditWallet}?amount={amount}"; + if (IsValidReturnUrl(returnUrl)) + url += $"&returnUrl={Uri.EscapeDataString(returnUrl!)}"; + return url; + } + + public static bool IsValidReturnUrl(string? url) => + !string.IsNullOrWhiteSpace(url) + && url.StartsWith('/') + && !url.StartsWith("//", StringComparison.Ordinal); + + public static bool IsInsufficientWalletBalanceMessage(string? message) => + message?.Contains(InsufficientBalanceMarker, StringComparison.Ordinal) == true; + + public static bool IsInsufficientWalletBalance(Exception ex) => + ex switch + { + RpcException rpc => IsInsufficientWalletBalanceMessage(rpc.Status.Detail) + || IsInsufficientWalletBalanceMessage(rpc.Message), + _ => IsInsufficientWalletBalanceMessage(ex.Message) + }; + + public static async Task SaveReturnUrlAsync(IJSRuntime js, string returnUrl) + { + if (!IsValidReturnUrl(returnUrl)) return; + await js.InvokeVoidAsync("sessionStorage.setItem", ReturnUrlStorageKey, returnUrl); + } + + public static async Task GetReturnUrlAsync(IJSRuntime js) => + await js.InvokeAsync("sessionStorage.getItem", ReturnUrlStorageKey); + + public static async Task ClearReturnUrlAsync(IJSRuntime js) => + await js.InvokeVoidAsync("sessionStorage.removeItem", ReturnUrlStorageKey); +}