feat(profile): enhance ChargeCreditWallet with return URL handling and continue shopping button
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 22m27s
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 22m27s
- 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.
This commit is contained in:
@@ -13,7 +13,16 @@
|
|||||||
Class="rounded-lg" Variant="Variant.Filled">
|
Class="rounded-lg" Variant="Variant.Filled">
|
||||||
@if (_paymentResult == "success")
|
@if (_paymentResult == "success")
|
||||||
{
|
{
|
||||||
<span>شارژ کیف پول اصلی با موفقیت انجام شد! ✅</span>
|
<MudStack Spacing="2">
|
||||||
|
<span>شارژ کیف پول اصلی با موفقیت انجام شد! ✅</span>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Small"
|
||||||
|
StartIcon="@Icons.Material.Filled.ShoppingCart"
|
||||||
|
OnClick="ContinueShoppingAsync">
|
||||||
|
ادامه خرید
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
}
|
}
|
||||||
else if (_paymentResult == "cancelled")
|
else if (_paymentResult == "cancelled")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using FrontOffice.Main.Utilities;
|
using FrontOffice.Main.Utilities;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
|
|
||||||
namespace FrontOffice.Main.Pages.Profile;
|
namespace FrontOffice.Main.Pages.Profile;
|
||||||
@@ -9,15 +10,38 @@ public partial class ChargeCreditWallet : ComponentBase
|
|||||||
private bool _isProcessing;
|
private bool _isProcessing;
|
||||||
private long _chargeAmount;
|
private long _chargeAmount;
|
||||||
private string? _paymentResult;
|
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 };
|
private readonly long[] _presetAmounts = { 500_000, 1_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000 };
|
||||||
|
|
||||||
[SupplyParameterFromQuery(Name = "payment")]
|
[SupplyParameterFromQuery(Name = "payment")]
|
||||||
public string? PaymentQueryParam { get; set; }
|
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()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
_paymentResult = PaymentQueryParam;
|
_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()
|
private async Task StartCreditCharge()
|
||||||
@@ -29,6 +53,15 @@ public partial class ChargeCreditWallet : ComponentBase
|
|||||||
|
|
||||||
try
|
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);
|
var (success, gatewayUrl, error) = await WalletService.InitiateCreditChargeAsync(_chargeAmount);
|
||||||
|
|
||||||
if (success && !string.IsNullOrEmpty(gatewayUrl))
|
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)
|
private static string FormatToman(long toman)
|
||||||
=> string.Format("{0:N0} تومان", toman);
|
=> string.Format("{0:N0} تومان", toman);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
using CMSMicroservice.Protobuf.Protos.UserAddress;
|
||||||
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
using CMSMicroservice.Protobuf.Protos.UserOrder;
|
||||||
|
using FrontOffice.Main.Shared;
|
||||||
using FrontOffice.Main.Utilities;
|
using FrontOffice.Main.Utilities;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
@@ -33,7 +34,6 @@ public partial class CheckoutSummary : ComponentBase
|
|||||||
{
|
{
|
||||||
await AuthDialogService.ShowAuthDialogAsync();
|
await AuthDialogService.ShowAuthDialogAsync();
|
||||||
}
|
}
|
||||||
// لود سبد خرید (فقط اگر کاربر لاگین کرده باشد)
|
|
||||||
await Cart.EnsureInitializedAsync();
|
await Cart.EnsureInitializedAsync();
|
||||||
await LoadAddresses();
|
await LoadAddresses();
|
||||||
await LoadWalletBalance();
|
await LoadWalletBalance();
|
||||||
@@ -43,7 +43,6 @@ public partial class CheckoutSummary : ComponentBase
|
|||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
// بارگذاری نرخ VAT
|
|
||||||
await VAT.LoadAsync();
|
await VAT.LoadAsync();
|
||||||
}
|
}
|
||||||
await base.OnAfterRenderAsync(firstRender);
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
@@ -52,9 +51,7 @@ public partial class CheckoutSummary : ComponentBase
|
|||||||
private async Task LoadWalletBalance()
|
private async Task LoadWalletBalance()
|
||||||
{
|
{
|
||||||
var walletResult = await WalletService.GetBalancesAsync();
|
var walletResult = await WalletService.GetBalancesAsync();
|
||||||
walletBalance = walletResult.CreditBalance
|
walletBalance = walletResult.CreditBalance;
|
||||||
// + walletResult.NetworkBalance
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadAddresses()
|
private async Task LoadAddresses()
|
||||||
@@ -93,11 +90,15 @@ public partial class CheckoutSummary : ComponentBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var totalRequired = VAT.AddVAT(Cart.Total);
|
||||||
|
if (await TryHandleInsufficientBalanceAsync(totalRequired))
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var request = new SubmitShopBuyOrderRequest
|
var request = new SubmitShopBuyOrderRequest
|
||||||
{
|
{
|
||||||
TotalAmount = VAT.AddVAT(Cart.Total)
|
TotalAmount = totalRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
var response = await UserOrderContract.SubmitShopBuyOrderAsync(request);
|
var response = await UserOrderContract.SubmitShopBuyOrderAsync(request);
|
||||||
@@ -107,17 +108,56 @@ public partial class CheckoutSummary : ComponentBase
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
if (CreditChargeNavigation.IsInsufficientWalletBalance(ex))
|
||||||
|
{
|
||||||
|
await LoadWalletBalance();
|
||||||
|
await TryHandleInsufficientBalanceAsync(totalRequired);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Snackbar.Add($"خطا در ثبت سفارش: {ex.Message}", Severity.Error);
|
Snackbar.Add($"خطا در ثبت سفارش: {ex.Message}", Severity.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> TryHandleInsufficientBalanceAsync(long totalRequired)
|
||||||
|
{
|
||||||
|
if (walletBalance >= totalRequired)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var shortfall = totalRequired - walletBalance;
|
||||||
|
var parameters = new DialogParameters<InsufficientCreditDialog>
|
||||||
|
{
|
||||||
|
{ x => x.CurrentBalance, walletBalance },
|
||||||
|
{ x => x.RequiredAmount, totalRequired },
|
||||||
|
{ x => x.ShortfallAmount, shortfall }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = await DialogService.ShowAsync<InsufficientCreditDialog>(
|
||||||
|
"موجودی کافی نیست",
|
||||||
|
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 static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// محاسبه مالیات بر ارزش افزوده
|
|
||||||
/// </summary>
|
|
||||||
private long CalculateVAT() => VAT.CalculateVAT(Cart.Total);
|
private long CalculateVAT() => VAT.CalculateVAT(Cart.Total);
|
||||||
|
|
||||||
private static string GetProductImageUrl(string? imageUrl)
|
private static string GetProductImageUrl(string? imageUrl)
|
||||||
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
|
=> string.IsNullOrWhiteSpace(imageUrl) ? "/images/product-placeholder.svg" : imageUrl.TrimStart('/');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<MudDialog>
|
||||||
|
<TitleContent>
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.AccountBalanceWallet" Color="Color.Warning" />
|
||||||
|
<MudText Typo="Typo.h6">موجودی کیف پول اصلی کافی نیست</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</TitleContent>
|
||||||
|
<DialogContent>
|
||||||
|
<MudStack Spacing="2">
|
||||||
|
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||||
|
برای تکمیل این سفارش، موجودی اصلی شما کافی نیست. میتوانید کیف پول را شارژ کنید و سپس سفارش را ثبت کنید.
|
||||||
|
</MudText>
|
||||||
|
<MudPaper Outlined="true" Class="pa-3 rounded-lg">
|
||||||
|
<MudStack Spacing="1">
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2">موجودی فعلی:</MudText>
|
||||||
|
<MudText Typo="Typo.body2"><strong>@FormatPrice(CurrentBalance)</strong></MudText>
|
||||||
|
</MudStack>
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2">مبلغ سفارش:</MudText>
|
||||||
|
<MudText Typo="Typo.body2"><strong>@FormatPrice(RequiredAmount)</strong></MudText>
|
||||||
|
</MudStack>
|
||||||
|
<MudDivider Class="my-1" />
|
||||||
|
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Error">کمبود:</MudText>
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Error"><strong>@FormatPrice(ShortfallAmount)</strong></MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
</MudStack>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton OnClick="Cancel">بستن</MudButton>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
StartIcon="@Icons.Material.Filled.Payment"
|
||||||
|
OnClick="GoToCharge">
|
||||||
|
شارژ حساب اصلی
|
||||||
|
</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
@@ -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<string?> GetReturnUrlAsync(IJSRuntime js) =>
|
||||||
|
await js.InvokeAsync<string?>("sessionStorage.getItem", ReturnUrlStorageKey);
|
||||||
|
|
||||||
|
public static async Task ClearReturnUrlAsync(IJSRuntime js) =>
|
||||||
|
await js.InvokeVoidAsync("sessionStorage.removeItem", ReturnUrlStorageKey);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user