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);
+}