feat(T4.2+T4.3+F3): conditional payment, re-purchase, PV display
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 2m33s

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
This commit is contained in:
masoodafar-web
2026-02-26 22:01:55 +03:30
parent a956cb90a1
commit 3bffc13ffd
8 changed files with 180 additions and 10 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
<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.187" /> <PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.188" />
<!-- <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" />
+22 -1
View File
@@ -207,11 +207,32 @@
FullWidth="true" FullWidth="true"
StartIcon="@Icons.Material.Filled.Payment" StartIcon="@Icons.Material.Filled.Payment"
OnClick="ProcessPayment" OnClick="ProcessPayment"
Disabled="@(!CanProceedToPayment || _isProcessingPayment)" Disabled="@(!CanProceedToPayment || _isProcessingPayment || !_selectedPackage.SupportsDirectPurchase)"
Class="mt-2"> Class="mt-2">
@(_isProcessingPayment ? "در حال پردازش..." : "پرداخت آنلاین") @(_isProcessingPayment ? "در حال پردازش..." : "پرداخت آنلاین")
</MudButton> </MudButton>
@if (_selectedPackage.SupportsDayaPurchase)
{
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Size="Size.Large"
FullWidth="true"
StartIcon="@Icons.Material.Filled.Diamond"
OnClick="DayaLoanPayment"
Disabled="@(!CanProceedToPayment)"
Class="mt-2">
تأمین اعتبار الماسی دایا
</MudButton>
}
@if (!_selectedPackage.SupportsDirectPurchase && !_selectedPackage.SupportsDayaPurchase)
{
<MudAlert Severity="Severity.Warning" Dense="true" Class="mt-2">
این پکیج در حال حاضر قابل خرید نیست.
</MudAlert>
}
@if (!CanProceedToPayment) @if (!CanProceedToPayment)
{ {
<MudText Typo="Typo.caption" Color="Color.Error" Align="Align.Center"> <MudText Typo="Typo.caption" Color="Color.Error" Align="Align.Center">
+25 -8
View File
@@ -3,6 +3,7 @@ using CMSMicroservice.Protobuf.Protos.Package;
using CMSMicroservice.Protobuf.Protos.UserAddress; using CMSMicroservice.Protobuf.Protos.UserAddress;
using FrontOffice.Main.Utilities; using FrontOffice.Main.Utilities;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using MudBlazor; using MudBlazor;
using Severity = MudBlazor.Severity; using Severity = MudBlazor.Severity;
@@ -10,6 +11,7 @@ namespace FrontOffice.Main.Pages;
public partial class Checkout public partial class Checkout
{ {
[Inject] private PackageService PackageService { get; set; } = default!;
[Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!; [Inject] private PackageContract.PackageContractClient PackageClient { get; set; } = default!;
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!; [Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
@@ -47,15 +49,18 @@ public partial class Checkout
{ {
try try
{ {
var response = await PackageClient.GetPackageAsync(new() { Id = PackageId.Value }); var packages = await PackageService.GetAllPackagesAsync();
if (response != null) var pkg = packages.FirstOrDefault(p => p.Id == PackageId.Value);
if (pkg != null)
{ {
_selectedPackage = new Pack( _selectedPackage = new Pack(
Id: response.Id, Id: pkg.Id,
Title: response.Title, Title: pkg.Title,
Body: response.Description, Body: pkg.Description,
Image: response.ImagePath ?? string.Empty, Image: pkg.ImageUrl,
Price: response.Price Price: pkg.Price,
SupportsDirectPurchase: pkg.SupportsDirectPurchase,
SupportsDayaPurchase: pkg.SupportsDayaPurchase
); );
_finalPrice = _selectedPackage.Price; _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);
} }
@@ -35,6 +35,70 @@
else else
{ {
<MudGrid Spacing="4"> <MudGrid Spacing="4">
@* Re-Purchase / Cycle Progress Section *@
@if (_canRepurchase)
{
<MudItem xs="12">
<MudAlert Severity="Severity.Success" Icon="@Icons.Material.Filled.Celebration" Class="mb-0">
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Style="width:100%">
<MudText Typo="Typo.body1">
🎉 چرخه جادویی تکمیل شد! می‌توانید پکیج جدید بخرید.
</MudText>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.ShoppingCart"
OnClick="@(() => Navigation.NavigateTo(RouteConstants.Package.List))">
خرید پکیج جدید
</MudButton>
</MudStack>
</MudAlert>
</MudItem>
}
else if (_magicStatus != null && _magicStatus.WalletMode == 1)
{
<MudItem xs="12">
<MudPaper Elevation="2" Class="pa-6">
<MudStack Spacing="3">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.AutoAwesome" Color="Color.Warning" />
<MudText Typo="Typo.h6">پیشرفت چرخه جادویی</MudText>
@if (_magicStatus.PurchaseCycleCount > 0)
{
<MudChip T="string" Size="Size.Small" Color="Color.Info">دور @(_magicStatus.PurchaseCycleCount + 1)</MudChip>
}
</MudStack>
<MudDivider />
<MudStack Spacing="2">
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="mud-text-secondary">واریز انجام‌شده:</MudText>
<MudText Typo="Typo.body2">@FormatPrice(_magicStatus.MagicTotalDeposited)</MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="mud-text-secondary">سقف واریز:</MudText>
<MudText Typo="Typo.body2">@FormatPrice(_magicStatus.MagicMaxDeposit)</MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="mud-text-secondary">باقیمانده:</MudText>
<MudText Typo="Typo.body2" Color="Color.Warning">@FormatPrice(_magicStatus.MagicRemainingDeposit)</MudText>
</MudStack>
<MudProgressLinear Color="Color.Primary" Value="@GetDepositProgress()" Class="my-2" Rounded="true" Size="Size.Large">
<MudText Typo="Typo.caption"><b>@GetDepositProgress()%</b></MudText>
</MudProgressLinear>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="mud-text-secondary">اعتبار دریافتی:</MudText>
<MudText Typo="Typo.body2" Color="Color.Success">@FormatPrice(_magicStatus.MagicTotalCredited)</MudText>
</MudStack>
</MudStack>
</MudStack>
</MudPaper>
</MudItem>
}
<!-- Package Status Card --> <!-- Package Status Card -->
<MudItem xs="12" md="6"> <MudItem xs="12" md="6">
<MudPaper Elevation="2" Class="pa-6 h-100"> <MudPaper Elevation="2" Class="pa-6 h-100">
@@ -9,7 +9,9 @@ public partial class MyPackages : ComponentBase
[Inject] private PackageService PackageService { get; set; } = default!; [Inject] private PackageService PackageService { get; set; } = default!;
private UserPackageStatusDto? _userStatus; private UserPackageStatusDto? _userStatus;
private MagicWalletStatus? _magicStatus;
private bool _isLoading = true; private bool _isLoading = true;
private bool _canRepurchase = false;
private List<BreadcrumbItem> _breadcrumbItems = new() private List<BreadcrumbItem> _breadcrumbItems = new()
{ {
@@ -30,6 +32,18 @@ public partial class MyPackages : ComponentBase
try try
{ {
_userStatus = await PackageService.GetUserPackageStatusAsync(); _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) catch (Exception ex)
{ {
@@ -67,4 +81,11 @@ public partial class MyPackages : ComponentBase
{ {
return string.Format("{0:N0} تومان", price); 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);
}
} }
@@ -112,6 +112,30 @@ else
</MudStack> </MudStack>
} }
</MudStack> </MudStack>
@* نمایش PV سفارش *@
@if (_pvData != null && _pvData.TotalPv > 0)
{
<MudDivider Class="my-2" />
<MudStack Spacing="1" Class="pa-2" Style="background-color: var(--mud-palette-background-grey); border-radius: 8px;">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<MudIcon Icon="@Icons.Material.Filled.TrendingUp" Color="Color.Info" Size="Size.Small" />
<MudText Typo="Typo.subtitle2" Color="Color.Info">امتیاز فروش (PV)</MudText>
</MudStack>
@foreach (var product in _pvData.Products)
{
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.caption" Class="mud-text-secondary">@product.ProductTitle × @product.Quantity</MudText>
<MudText Typo="Typo.caption">@string.Format("{0:N0}", product.TotalPv) PV</MudText>
</MudStack>
}
<MudDivider Class="my-1" />
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Class="fw-bold">جمع PV:</MudText>
<MudText Typo="Typo.body2" Color="Color.Info" Class="fw-bold">@string.Format("{0:N0}", _pvData.TotalPv) PV</MudText>
</MudStack>
</MudStack>
}
</MudStack> </MudStack>
</MudPaper> </MudPaper>
</MudContainer> </MudContainer>
@@ -12,12 +12,17 @@ public partial class OrderDetail : ComponentBase
[Parameter] public long id { get; set; } [Parameter] public long id { get; set; }
private GetUserOrderResponse? _order; private GetUserOrderResponse? _order;
private CalculateOrderPVResponse? _pvData;
private bool _loading; private bool _loading;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
_loading = true; _loading = true;
_order = await OrderService.GetOrderAsync(id); _order = await OrderService.GetOrderAsync(id);
if (_order != null)
{
_pvData = await OrderService.CalculateOrderPVAsync(id);
}
_loading = false; _loading = false;
} }
@@ -97,4 +97,22 @@ public class OrderService
return 0.10; return 0.10;
} }
} }
/// <summary>
/// محاسبه PV سفارش
/// </summary>
public async Task<CalculateOrderPVResponse?> CalculateOrderPVAsync(long orderId)
{
try
{
return await _userOrderContractClient.CalculateOrderPVAsync(new CalculateOrderPVRequest
{
OrderId = orderId
});
}
catch
{
return null;
}
}
} }