diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj index 0e2ce06..cbd5f4e 100644 --- a/src/FrontOffice.Main/FrontOffice.Main.csproj +++ b/src/FrontOffice.Main/FrontOffice.Main.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor b/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor index 23d468b..28da840 100644 --- a/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor +++ b/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor @@ -23,9 +23,9 @@ محصول قیمت واحد - سقف تخفیف + سقف اعتبار تعداد - تخفیف + اعتبار قیمت نهایی @@ -40,7 +40,7 @@ @FormatPrice(context.UnitPrice) - @context.MaxDiscountPercent% تخفیف + @context.MaxDiscountPercent% اعتبار @@ -86,7 +86,7 @@ @item.Title - @item.MaxDiscountPercent% تخفیف + @item.MaxDiscountPercent% اعتبار @@ -107,7 +107,7 @@ @if (item.DiscountAmount > 0) { - تخفیف: @FormatPrice(item.DiscountAmount)- + اعتبار: @FormatPrice(item.DiscountAmount)- } جمع: @FormatPrice(item.FinalPrice) 0) { - سقف تخفیف قابل اعمال: + سقف اعتبار قابل اعمال: @FormatPrice(DiscountCart.TotalDiscount)- تومان } + @if (VAT.IsEnabled) + { + + مالیات ارزش افزوده (@VAT.VatPercentage%): + @FormatPrice(VatAmount)+ تومان + + } حداقل پرداخت: - @FormatPrice(DiscountCart.FinalPrice) تومان + @FormatPrice(TotalWithVat) تومان @@ -161,14 +168,21 @@ @if (DiscountCart.TotalDiscount > 0) { - سقف تخفیف قابل اعمال: + سقف اعتبار قابل اعمال: @FormatPrice(DiscountCart.TotalDiscount)- تومان } + @if (VAT.IsEnabled) + { + + مالیات بر ارزش افزوده (@VAT.VatPercentage%): + @FormatPrice(VatAmount)+ تومان + + } حداقل مبلغ پرداخت درگاه: - @FormatPrice(DiscountCart.FinalPrice) تومان + @FormatPrice(TotalWithVat) تومان مبلغ دقیق پرداخت در مرحله تسویه مشخص خواهد شد. diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor.cs b/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor.cs index 256e9cb..d53ea46 100644 --- a/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor.cs +++ b/src/FrontOffice.Main/Pages/DiscountStore/Cart.razor.cs @@ -6,6 +6,7 @@ namespace FrontOffice.Main.Pages.DiscountStore; public partial class Cart : IDisposable { [Inject] private DiscountCartService DiscountCart { get; set; } = default!; + [Inject] private VATService VAT { get; set; } = default!; protected override async Task OnInitializedAsync() { @@ -44,6 +45,18 @@ public partial class Cart : IDisposable Navigation.NavigateTo(RouteConstants.DiscountStore.Checkout); } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await VAT.LoadAsync(); + StateHasChanged(); + } + } + + private long VatAmount => VAT.CalculateVAT(DiscountCart.FinalPrice); + private long TotalWithVat => DiscountCart.FinalPrice + VatAmount; + private static string FormatPrice(long price) => $"{price:N0} "; private static string GetImageUrl(string? imageUrl) diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor index 6158341..beba9e2 100644 --- a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor +++ b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor @@ -90,7 +90,7 @@ @if (item.MaxDiscountPercent > 0) { - @item.MaxDiscountPercent% تخفیف + @item.MaxDiscountPercent% اعتبار } @@ -108,17 +108,17 @@ - تخفیف از کیف اعتباری: + اعتبار از کیف اعتباری: @FormatPrice(DiscountCart.TotalDiscount)- تومان - مبلغ پس از تخفیف: + مبلغ پس از اعتبار: @FormatPrice(NetGatewayAmount) تومان - مالیات بر ارزش افزوده (۱۰٪): + مالیات بر ارزش افزوده (@VAT.VatPercentage٪): @FormatPrice(VatAmount)+ تومان diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs index 9995b32..4036725 100644 --- a/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs +++ b/src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs @@ -10,6 +10,7 @@ public partial class Checkout [Inject] private DiscountCartService DiscountCart { get; set; } = default!; [Inject] private DiscountOrderService DiscountOrderService { get; set; } = default!; [Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!; + [Inject] private VATService VAT { get; set; } = default!; private List _addresses = new(); private CustomerAddressModel? _selectedAddress; @@ -18,13 +19,11 @@ public partial class Checkout private string? _notes; private bool _placing; - private const decimal VAT_RATE = 0.10m; - - /// مبلغ درگاه قبل از مالیات (جمع کل - تخفیف) + /// مبلغ درگاه قبل از مالیات (جمع کل - اعتبار) private long NetGatewayAmount => DiscountCart.TotalPrice - DiscountCart.TotalDiscount; - /// مالیات بر ارزش افزوده ۱۰٪ - private long VatAmount => (long)(NetGatewayAmount * VAT_RATE); + /// مالیات بر ارزش افزوده + private long VatAmount => VAT.CalculateVAT(NetGatewayAmount); /// مبلغ نهایی قابل پرداخت (شامل VAT) private long FinalGatewayAmount => NetGatewayAmount + VatAmount; @@ -33,6 +32,7 @@ public partial class Checkout protected override async Task OnInitializedAsync() { + await VAT.LoadAsync(); await DiscountCart.EnsureInitializedAsync(); await LoadAddresses(); } diff --git a/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor b/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor index db96ab8..528843c 100644 --- a/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor +++ b/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor @@ -82,8 +82,8 @@ else محصول قیمت واحد تعداد - سقف تخفیف - تخفیف + سقف اعتبار + اعتبار قیمت نهایی @@ -131,7 +131,7 @@ else @if (item.DiscountAmount > 0) { - تخفیف (@item.MaxDiscountPercent%): + اعتبار (@item.MaxDiscountPercent%): @FormatPrice(item.DiscountAmount)- } @@ -163,6 +163,14 @@ else } + @if (VAT.IsEnabled) + { + + مالیات بر ارزش افزوده (@VAT.VatPercentage%): + @FormatPrice(OrderVatAmount)+ + + } + diff --git a/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor.cs b/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor.cs index 4c57dac..bcffac9 100644 --- a/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor.cs +++ b/src/FrontOffice.Main/Pages/DiscountStore/OrderDetail.razor.cs @@ -12,12 +12,28 @@ public partial class OrderDetail public string? PaymentResult { get; set; } [Inject] private DiscountOrderService DiscountOrderService { get; set; } = default!; + [Inject] private VATService VAT { get; set; } = default!; private DiscountOrderDetail? _order; private bool _loading = true; private string? _paymentMessage; private MudBlazor.Severity _paymentSeverity; + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await VAT.LoadAsync(); + StateHasChanged(); + } + } + + /// مبلغ قبل از مالیات (جمع کل - اعتبار) + private long PreVatGateway => _order is null ? 0 : (_order.TotalPrice - _order.DiscountBalanceUsed); + + /// مالیات بر ارزش افزوده + private long OrderVatAmount => VAT.CalculateVAT(PreVatGateway); + protected override async Task OnInitializedAsync() { // نمایش پیام نتیجه پرداخت (بعد از بازگشت از درگاه) diff --git a/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor b/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor index 0b582e4..db89b9e 100644 --- a/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor +++ b/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor @@ -67,14 +67,38 @@ قیمت محصول: - + @($"{_product.Price:N0}") تومان + @if (VAT.IsEnabled) + { + + مالیات ارزش افزوده (@VAT.VatPercentage%): + + @($"{VAT.CalculateVAT(_product.Price):N0}") تومان + + + + قیمت با مالیات: + + @($"{VAT.AddVAT(_product.Price):N0}") تومان + + + } + else + { + + قیمت نهایی: + + @($"{_product.Price:N0}") تومان + + + } @if (_product.MaxDiscountPercent > 0) { - تخفیف از کیف اعتباری: + اعتبار از کیف اعتباری: @_product.MaxDiscountPercent% (@($"{_product.Price * _product.MaxDiscountPercent / 100:N0}") تومان) diff --git a/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor.cs b/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor.cs index fd90d66..5d09884 100644 --- a/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor.cs +++ b/src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor.cs @@ -9,6 +9,7 @@ public partial class ProductDetail [Inject] private DiscountProductService DiscountProductService { get; set; } = default!; [Inject] private DiscountCartService DiscountCartService { get; set; } = default!; + [Inject] private VATService VAT { get; set; } = default!; private DiscountProductDetail? _product; private string _selectedImage = string.Empty; @@ -21,6 +22,15 @@ public partial class ProductDetail await LoadProductAsync(); } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await VAT.LoadAsync(); + StateHasChanged(); + } + } + private async Task LoadProductAsync() { _loading = true; diff --git a/src/FrontOffice.Main/Pages/DiscountStore/Products.razor b/src/FrontOffice.Main/Pages/DiscountStore/Products.razor index 6cb49e8..5630c14 100644 --- a/src/FrontOffice.Main/Pages/DiscountStore/Products.razor +++ b/src/FrontOffice.Main/Pages/DiscountStore/Products.razor @@ -97,13 +97,16 @@ { - @p.MaxDiscountPercent% تخفیف + @p.MaxDiscountPercent% اعتبار }
@p.Title - @FormatPrice(p.Price) +
+ @FormatPrice(p.Price) + (+ ارزش افزوده) +
diff --git a/src/FrontOffice.Main/Pages/Licenses.razor b/src/FrontOffice.Main/Pages/Licenses.razor index bef2351..658cec4 100644 --- a/src/FrontOffice.Main/Pages/Licenses.razor +++ b/src/FrontOffice.Main/Pages/Licenses.razor @@ -37,7 +37,7 @@ else @foreach (var license in _licenseImages) { - + diff --git a/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor b/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor new file mode 100644 index 0000000..0847eae --- /dev/null +++ b/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor @@ -0,0 +1,121 @@ +@attribute [Route(RouteConstants.Profile.ChargeDiscountWallet)] +@attribute [Authorize] + +شارژ کیف‌پول اعتباری + + + + + + @* ─── نتیجه پرداخت (اگه از callback برگشته) ─── *@ + @if (_paymentResult != null) + { + + @if (_paymentResult == "success") + { + شارژ اعتباری با موفقیت انجام شد! ✅ + } + else if (_paymentResult == "cancelled") + { + پرداخت توسط شما لغو شد. + } + else + { + پرداخت ناموفق بود. لطفاً دوباره تلاش کنید. + } + + } + + @* ─── فرم شارژ ─── *@ + + + + شارژ موجودی اعتباری + + + + مبلغ وارد شده (به تومان) از طریق درگاه پرداخت به موجودی اعتباری شما اضافه می‌شود. + از موجودی اعتباری فقط برای خرید از فروشگاه اعتباری می‌توانید استفاده کنید. + + + + + + @* دکمه‌های مبلغ سریع *@ + + @foreach (var preset in _presetAmounts) + { + + @FormatToman(preset) + + } + + + @if (_chargeAmount > 0) + { + + + مبلغ پرداخت: + @FormatToman(_chargeAmount) + + + اعتبار دریافتی: + @FormatToman(_chargeAmount) + + + } + + + @if (_isProcessing) + { + + در حال انتقال به درگاه... + } + else + { + پرداخت با کارت بانکی + } + + + + + @* ─── قوانین ─── *@ + + + + + موجودی اعتباری فقط قابل استفاده در فروشگاه اعتباری است + + + حداقل مبلغ شارژ: ۱۰,۰۰۰ تومان + + + مبلغ واریزی بدون ضریب به موجودی اضافه می‌شود (۱:۱) + + + پرداخت از طریق درگاه بانکی انجام می‌شود + + + + + + diff --git a/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor.cs b/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor.cs new file mode 100644 index 0000000..1d3ffaf --- /dev/null +++ b/src/FrontOffice.Main/Pages/Profile/ChargeDiscountWallet.razor.cs @@ -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); +} diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor b/src/FrontOffice.Main/Pages/Profile/Index.razor index 0a0761b..2ca1a37 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor @@ -53,7 +53,7 @@
- تخفیف باشگاه + کیف اعتباری
@_walletDiscount
@@ -214,26 +214,65 @@ پرداخت مستقیم
پرداخت آنی از طریق درگاه بانکی - - پرداخت با کارت بانکی(بزودی) - + @if (_purchaseCycleCount > 0) + { + + @if (_isProcessingPayment) + { + + در حال انتقال به درگاه... + } + else + { + پرداخت با کارت بانکی + } + + } + else + { + + @if (_isProcessingPayment) + { + + در حال انتقال به درگاه... + } + else + { + پرداخت با کارت بانکی + } + + }
- - - - - اعتبار الماسی دایا + @if (_purchaseCycleCount == 0) + { + + + + + اعتبار الماسی دایا + + تأمین اعتبار از طریق خرید توکن الماس و دریافت وام + + تأمین اعتبار الماسی + - تأمین اعتبار از طریق خرید توکن الماس و دریافت وام - - تأمین اعتبار الماسی - - - + + } + else + { + + از دور دوم به بعد، خرید پکیج فقط از طریق درگاه بانکی امکان‌پذیر است. + + } بستن diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs index 2b47695..f64e0d5 100644 --- a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs @@ -45,6 +45,7 @@ public partial class Index // Purchase Options Bottom Sheet private bool _showPurchaseBottomSheet; private bool _isProcessingPayment; + private int _purchaseCycleCount; // تعداد دورهای خرید — اگر > 0 فقط IPG مجاز // Dashboard Tiles private readonly List _dashTiles = new() @@ -148,6 +149,11 @@ public partial class Index var discount = FormatPrice(b.DiscountBalance); _walletNetwork = FormatPrice(b.NetworkBalance); _walletDiscount = discount; + + // بارگذاری تعداد دورهای خرید برای کنترل روش‌های پرداخت + var magicStatus = await WalletService.GetMagicWalletStatusAsync(); + _purchaseCycleCount = magicStatus.PurchaseCycleCount; + StateHasChanged(); } diff --git a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor index a5fd7de..839d38a 100644 --- a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor +++ b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor @@ -105,7 +105,7 @@ Format="N0" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Payments" - HelperText="@($"حداکثر: {FormatPrice(_status.MagicRemainingDeposit)} (ریال)")" /> + HelperText="@($"حداکثر: {FormatPrice(_status.MagicRemainingDeposit)} (تومان)")" /> @if (_chargeAmount > 0) { @@ -141,18 +141,18 @@ Color="Color.Primary" Size="Size.Large" FullWidth="true" - Disabled="@(_chargeAmount <= 0 || _isProcessing)" + Disabled="@(_isProcessing || _chargeAmount < 10_000)" OnClick="StartMagicCharge" StartIcon="@Icons.Material.Filled.Payment" Class="rounded-lg mt-2"> @if (_isProcessing) { - + در حال انتقال به درگاه... } else { - پرداخت از درگاه + پرداخت با کارت بانکی }
diff --git a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor.cs b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor.cs index d5bf1d8..1b26077 100644 --- a/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor.cs +++ b/src/FrontOffice.Main/Pages/Profile/MagicWallet.razor.cs @@ -7,7 +7,7 @@ namespace FrontOffice.Main.Pages.Profile; 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 _isProcessing; private long _chargeAmount; diff --git a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor index 89e42ae..952d7f1 100644 --- a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor +++ b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor @@ -34,7 +34,7 @@ موجودی کیف پول: @FormatPrice(_walletBalance) - موجودی اعتبار تخفیف: @FormatPrice(_discountBalance) + موجودی کیف پول اعتباری: @FormatPrice(_discountBalance) + + + شارژ کیف‌پول اعتباری + + تراکنش‌ها و مسیرهای شارژ diff --git a/src/FrontOffice.Main/Utilities/RouteConstants.cs b/src/FrontOffice.Main/Utilities/RouteConstants.cs index a1de966..0c57869 100644 --- a/src/FrontOffice.Main/Utilities/RouteConstants.cs +++ b/src/FrontOffice.Main/Utilities/RouteConstants.cs @@ -23,6 +23,7 @@ public static class RouteConstants public const string Tree = "/profile/tree"; public const string Wallet = "/profile/wallet"; public const string MagicWallet = "/profile/magic-wallet"; + public const string ChargeDiscountWallet = "/profile/charge-discount-wallet"; public const string WithdrawalRequests = "/profile/withdrawal-requests"; } diff --git a/src/FrontOffice.Main/Utilities/WalletService.cs b/src/FrontOffice.Main/Utilities/WalletService.cs index ea77c60..5b0bcfb 100644 --- a/src/FrontOffice.Main/Utilities/WalletService.cs +++ b/src/FrontOffice.Main/Utilities/WalletService.cs @@ -32,7 +32,8 @@ public record MagicWalletStatus( long MagicMaxDeposit, long MagicRemainingDeposit, long Balance, - DateTime? MagicActivatedAt + DateTime? MagicActivatedAt, + int PurchaseCycleCount ); public class WalletService @@ -212,12 +213,13 @@ public class WalletService response.MagicMaxDeposit, response.MagicRemainingDeposit, response.Balance, - activatedAt + activatedAt, + response.PurchaseCycleCount ); } 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() { try