Compare commits

...

2 Commits

Author SHA1 Message Date
masoodafar-web 28695a88f8 feat: ChargeDiscountWallet UI - page, route, client method & wallet link
Build and Deploy to Kubernetes / build-and-deploy (push) Has been cancelled
- Add ChargeDiscountWallet.razor page with charge form and preset amounts
- Add ChargeDiscountWallet.razor.cs code-behind with gateway redirect logic
- Add InitiateDiscountChargeAsync client method in WalletService
- Add ChargeDiscountWallet route constant
- Add charge button link on main Wallet page
2026-02-22 20:22:52 +03:30
masoodafar-web a2e1e1fead feat: Magic Wallet Phase 6 UI + purchase cycle controls
- FrontOffice.Main.csproj: switch to ProjectReference for CMS Protobuf
- MagicWallet.razor.cs: update default record with PurchaseCycleCount
- WalletService.cs: add PurchaseCycleCount to MagicWalletStatus record
- Profile/Index.razor.cs: load _purchaseCycleCount from MagicWalletStatus
- Profile/Index.razor: conditional purchase dialog (IPG vs Daya by cycle)
- Licenses.razor: fix grid column size xs=6
2026-02-22 20:04:44 +03:30
10 changed files with 262 additions and 23 deletions
+2 -2
View File
@@ -11,8 +11,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" /> <PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
<!-- Replace all FrontOffice.BFF protobuf packages with CMS protobuf --> <!-- Replace all FrontOffice.BFF protobuf packages with CMS protobuf -->
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.181" /> <!-- <PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.181" />-->
<!-- <ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />--> <ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />
<PackageReference Include="MudBlazor" Version="8.14.0" /> <PackageReference Include="MudBlazor" Version="8.14.0" />
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" /> <PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageReference Include="Mapster" Version="7.4.0" /> <PackageReference Include="Mapster" Version="7.4.0" />
+1 -1
View File
@@ -37,7 +37,7 @@ else
<MudGrid Spacing="4" Justify="Justify.Center"> <MudGrid Spacing="4" Justify="Justify.Center">
@foreach (var license in _licenseImages) @foreach (var license in _licenseImages)
{ {
<MudItem xs="12" sm="6" md="4"> <MudItem xs="6" sm="6" md="4">
<MudCard Elevation="2" Class="h-100"> <MudCard Elevation="2" Class="h-100">
<MudCardMedia Image="@license.ImagePath" Height="250" /> <MudCardMedia Image="@license.ImagePath" Height="250" />
<MudCardContent> <MudCardContent>
@@ -0,0 +1,121 @@
@attribute [Route(RouteConstants.Profile.ChargeDiscountWallet)]
@attribute [Authorize]
<PageTitle>شارژ کیف‌پول اعتباری</PageTitle>
<MudContainer MaxWidth="MaxWidth.Medium" Class="py-6">
<MudStack Spacing="3">
<PageHeader Title="💳 شارژ کیف‌پول اعتباری" BackHref="@RouteConstants.Profile.Wallet" />
@* ─── نتیجه پرداخت (اگه از callback برگشته) ─── *@
@if (_paymentResult != null)
{
<MudAlert Severity="@(_paymentResult == "success" ? MudBlazor.Severity.Success : MudBlazor.Severity.Error)"
Class="rounded-lg" Variant="Variant.Filled">
@if (_paymentResult == "success")
{
<span>شارژ اعتباری با موفقیت انجام شد! ✅</span>
}
else if (_paymentResult == "cancelled")
{
<span>پرداخت توسط شما لغو شد.</span>
}
else
{
<span>پرداخت ناموفق بود. لطفاً دوباره تلاش کنید.</span>
}
</MudAlert>
}
@* ─── فرم شارژ ─── *@
<MudPaper Elevation="2" Class="pa-5 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-3">
<MudIcon Icon="@Icons.Material.Filled.CreditCard" Class="ml-1" />
شارژ موجودی اعتباری
</MudText>
<MudAlert Severity="MudBlazor.Severity.Info" Dense="true" Class="mb-3" Icon="@Icons.Material.Filled.Info">
مبلغ وارد شده (به تومان) از طریق درگاه پرداخت به موجودی اعتباری شما اضافه می‌شود.
از موجودی اعتباری فقط برای خرید از فروشگاه تخفیفی می‌توانید استفاده کنید.
</MudAlert>
<MudStack Spacing="2">
<MudNumericField @bind-Value="_chargeAmount"
Label="مبلغ (تومان)"
Variant="Variant.Outlined"
Min="10_000"
Max="100_000_000"
Format="N0"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Payments"
HelperText="حداقل ۱۰,۰۰۰ و حداکثر ۱۰۰,۰۰۰,۰۰۰ تومان" />
@* دکمه‌های مبلغ سریع *@
<MudStack Row="true" Spacing="1" Class="flex-wrap">
@foreach (var preset in _presetAmounts)
{
<MudButton Variant="Variant.Outlined" Size="Size.Small"
Color="@(_chargeAmount == preset ? Color.Primary : Color.Default)"
OnClick="() => _chargeAmount = preset"
Class="rounded-pill">
@FormatToman(preset)
</MudButton>
}
</MudStack>
@if (_chargeAmount > 0)
{
<MudPaper Outlined="true" Class="pa-3 rounded-lg" Style="background: #f0fdf4;">
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2">مبلغ پرداخت:</MudText>
<MudText Typo="Typo.body2"><strong>@FormatToman(_chargeAmount)</strong></MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2">اعتبار دریافتی:</MudText>
<MudText Typo="Typo.body2" Color="Color.Success"><strong>@FormatToman(_chargeAmount)</strong></MudText>
</MudStack>
</MudPaper>
}
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Size="Size.Large"
FullWidth="true"
Disabled="@(_chargeAmount <= 0 || _isProcessing)"
OnClick="StartDiscountCharge"
StartIcon="@Icons.Material.Filled.Payment"
Class="rounded-lg mt-2">
@if (_isProcessing)
{
<MudProgressCircular Indeterminate="true" Size="Size.Small" Class="ml-2" />
<span>در حال انتقال به درگاه...</span>
}
else
{
<span>پرداخت از درگاه</span>
}
</MudButton>
</MudStack>
</MudPaper>
@* ─── قوانین ─── *@
<MudExpansionPanels Elevation="1" Class="rounded-lg">
<MudExpansionPanel Text="قوانین شارژ اعتباری" MaxHeight="500" IsInitiallyExpanded="false">
<MudList T="string" Dense="true">
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
موجودی اعتباری فقط قابل استفاده در فروشگاه تخفیفی است
</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
حداقل مبلغ شارژ: ۱۰,۰۰۰ تومان
</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Filled.CheckCircle" IconColor="Color.Success">
مبلغ واریزی بدون ضریب به موجودی اضافه می‌شود (۱:۱)
</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Filled.Info" IconColor="Color.Info">
پرداخت از طریق درگاه بانکی انجام می‌شود
</MudListItem>
</MudList>
</MudExpansionPanel>
</MudExpansionPanels>
</MudStack>
</MudContainer>
@@ -0,0 +1,59 @@
using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace FrontOffice.Main.Pages.Profile;
public partial class ChargeDiscountWallet : ComponentBase
{
private bool _isProcessing;
private long _chargeAmount;
private string? _paymentResult;
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; }
protected override void OnInitialized()
{
_paymentResult = PaymentQueryParam;
}
private async Task StartDiscountCharge()
{
if (_chargeAmount <= 0 || _isProcessing) return;
_isProcessing = true;
StateHasChanged();
try
{
// تبدیل تومان به ریال
var amountInRial = _chargeAmount * 10;
var (success, gatewayUrl, error) = await WalletService.InitiateDiscountChargeAsync(amountInRial);
if (success && !string.IsNullOrEmpty(gatewayUrl))
{
Navigation.NavigateTo(gatewayUrl, forceLoad: true);
}
else
{
Snackbar.Add(error ?? "خطا در ایجاد درخواست پرداخت", Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add($"خطا: {ex.Message}", Severity.Error);
}
finally
{
_isProcessing = false;
StateHasChanged();
}
}
private static string FormatToman(long toman)
=> string.Format("{0:N0} تومان", toman);
}
@@ -214,13 +214,25 @@
<MudText Typo="Typo.subtitle1" Color="Color.Primary"><b>پرداخت مستقیم</b></MudText> <MudText Typo="Typo.subtitle1" Color="Color.Primary"><b>پرداخت مستقیم</b></MudText>
</MudStack> </MudStack>
<MudText Typo="Typo.body2" Class="mud-text-secondary">پرداخت آنی از طریق درگاه بانکی</MudText> <MudText Typo="Typo.body2" Class="mud-text-secondary">پرداخت آنی از طریق درگاه بانکی</MudText>
@if (_purchaseCycleCount > 0)
{
<MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="true"
StartIcon="@Icons.Material.Filled.Payment" OnClick="DirectPayment">
پرداخت با کارت بانکی
</MudButton>
}
else
{
<MudButton Variant="Variant.Filled" Disabled="true" Color="Color.Primary" FullWidth="true" <MudButton Variant="Variant.Filled" Disabled="true" Color="Color.Primary" FullWidth="true"
StartIcon="@Icons.Material.Filled.Payment" OnClick="DirectPayment"> StartIcon="@Icons.Material.Filled.Payment" OnClick="DirectPayment">
پرداخت با کارت بانکی(بزودی) پرداخت با کارت بانکی(بزودی)
</MudButton> </MudButton>
}
</MudStack> </MudStack>
</MudPaper> </MudPaper>
@if (_purchaseCycleCount == 0)
{
<MudPaper Elevation="0" Class="pa-4 rounded-lg" Style="border: 2px solid var(--mud-palette-tertiary);"> <MudPaper Elevation="0" Class="pa-4 rounded-lg" Style="border: 2px solid var(--mud-palette-tertiary);">
<MudStack Spacing="2"> <MudStack Spacing="2">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2"> <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
@@ -234,6 +246,13 @@
</MudButton> </MudButton>
</MudStack> </MudStack>
</MudPaper> </MudPaper>
}
else
{
<MudAlert Severity="Severity.Info" Dense="true" Class="mt-1">
از دور دوم به بعد، خرید پکیج فقط از طریق درگاه بانکی امکان‌پذیر است.
</MudAlert>
}
<MudButton Variant="Variant.Text" Color="Color.Default" FullWidth="true" OnClick="ClosePurchaseOptions"> <MudButton Variant="Variant.Text" Color="Color.Default" FullWidth="true" OnClick="ClosePurchaseOptions">
بستن بستن
@@ -45,6 +45,7 @@ public partial class Index
// Purchase Options Bottom Sheet // Purchase Options Bottom Sheet
private bool _showPurchaseBottomSheet; private bool _showPurchaseBottomSheet;
private bool _isProcessingPayment; private bool _isProcessingPayment;
private int _purchaseCycleCount; // تعداد دورهای خرید — اگر > 0 فقط IPG مجاز
// Dashboard Tiles // Dashboard Tiles
private readonly List<DashTile> _dashTiles = new() private readonly List<DashTile> _dashTiles = new()
@@ -148,6 +149,11 @@ public partial class Index
var discount = FormatPrice(b.DiscountBalance); var discount = FormatPrice(b.DiscountBalance);
_walletNetwork = FormatPrice(b.NetworkBalance); _walletNetwork = FormatPrice(b.NetworkBalance);
_walletDiscount = discount; _walletDiscount = discount;
// بارگذاری تعداد دورهای خرید برای کنترل روش‌های پرداخت
var magicStatus = await WalletService.GetMagicWalletStatusAsync();
_purchaseCycleCount = magicStatus.PurchaseCycleCount;
StateHasChanged(); StateHasChanged();
} }
@@ -7,7 +7,7 @@ namespace FrontOffice.Main.Pages.Profile;
public partial class MagicWallet : ComponentBase public partial class MagicWallet : ComponentBase
{ {
private MagicWalletStatus _status = new(0, 0, 0, 0, 0, 0, null); private MagicWalletStatus _status = new(0, 0, 0, 0, 0, 0, null, 0);
private bool _isLoading = true; private bool _isLoading = true;
private bool _isProcessing; private bool _isProcessing;
private long _chargeAmount; private long _chargeAmount;
@@ -38,6 +38,17 @@
درخواست‌های برداشت درخواست‌های برداشت
</MudButton> </MudButton>
<!-- دکمه شارژ کیف‌پول اعتباری -->
<MudButton Variant="Variant.Filled"
Color="Color.Info"
Size="Size.Large"
FullWidth="true"
StartIcon="@Icons.Material.Filled.CreditCard"
Href="@RouteConstants.Profile.ChargeDiscountWallet"
Class="rounded-lg">
شارژ کیف‌پول اعتباری
</MudButton>
<MudPaper Elevation="2" Class="pa-4 rounded-lg"> <MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-2">تراکنش‌ها و مسیرهای شارژ</MudText> <MudText Typo="Typo.h6" Class="mb-2">تراکنش‌ها و مسیرهای شارژ</MudText>
@@ -23,6 +23,7 @@ public static class RouteConstants
public const string Tree = "/profile/tree"; public const string Tree = "/profile/tree";
public const string Wallet = "/profile/wallet"; public const string Wallet = "/profile/wallet";
public const string MagicWallet = "/profile/magic-wallet"; public const string MagicWallet = "/profile/magic-wallet";
public const string ChargeDiscountWallet = "/profile/charge-discount-wallet";
public const string WithdrawalRequests = "/profile/withdrawal-requests"; public const string WithdrawalRequests = "/profile/withdrawal-requests";
} }
@@ -32,7 +32,8 @@ public record MagicWalletStatus(
long MagicMaxDeposit, long MagicMaxDeposit,
long MagicRemainingDeposit, long MagicRemainingDeposit,
long Balance, long Balance,
DateTime? MagicActivatedAt DateTime? MagicActivatedAt,
int PurchaseCycleCount
); );
public class WalletService public class WalletService
@@ -212,12 +213,13 @@ public class WalletService
response.MagicMaxDeposit, response.MagicMaxDeposit,
response.MagicRemainingDeposit, response.MagicRemainingDeposit,
response.Balance, response.Balance,
activatedAt activatedAt,
response.PurchaseCycleCount
); );
} }
catch catch
{ {
return new MagicWalletStatus(0, 0, 0, 0, 0, 0, null); return new MagicWalletStatus(0, 0, 0, 0, 0, 0, null, 0);
} }
} }
@@ -241,6 +243,26 @@ public class WalletService
} }
} }
public async Task<(bool Success, string? GatewayUrl, string? Error)> InitiateDiscountChargeAsync(long amount)
{
try
{
var response = await _client.InitiateDiscountChargeAsync(new InitiateDiscountChargeRequest
{
Amount = amount
});
if (response.IsSuccess)
return (true, response.GatewayUrl, null);
return (false, null, response.ErrorMessage);
}
catch (Grpc.Core.RpcException ex)
{
return (false, null, ex.Status.Detail ?? "خطا در ارتباط با سرور");
}
}
public int GetWalletMode() public int GetWalletMode()
{ {
try try