Files
FrontOffice/src/FrontOffice.Main/Pages/Profile/Index.razor.cs
T
masoodafar-web aedf312812
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 5m6s
feat(payment): integrate TwentyCard payment option and enhance checkout flow
- Added a new payment method for TwentyCard in the checkout process, allowing users to select it alongside direct payment options.
- Implemented the logic for processing TwentyCard payments, including user feedback and dialog interactions for payment confirmation.
- Updated the appsettings files to include configuration for the new payment method.
- Enhanced the user interface in the PackagePurchaseDialog to reflect the new payment option and its requirements.

These changes improve the payment flexibility in the checkout process, providing users with additional options for completing their transactions.
2026-09-20 22:17:39 +03:30

526 lines
19 KiB
C#

using FluentValidation;
using FrontOffice.Main.Pages.Profile.Components;
using FrontOffice.Main.Utilities;
using Mapster;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using MudBlazor;
using System.ComponentModel.DataAnnotations;
using CMSMicroservice.Protobuf.Protos.Package;
using CMSMicroservice.Protobuf.Protos.User;
using CMSMicroservice.Protobuf.Protos.UserAddress;
using CMSMicroservice.Protobuf.Validator.User;
using CMSMicroservice.Protobuf.Validator.UserAddress;
using Severity = MudBlazor.Severity;
namespace FrontOffice.Main.Pages.Profile;
public partial class Index
{
[Inject] private UserContract.UserContractClient UserContract { get; set; } = default!;
[Inject] private UserAddressContract.UserAddressContractClient UserAddressContract { get; set; } = default!;
[Inject] private PackageContract.PackageContractClient PackageContract { get; set; } = default!;
[Inject] private AuthService AuthService { get; set; } = default!;
[Inject] private BlogPostService BlogPostService { get; set; } = default!;
private GetUserForCustomerResponse _userProfile = new();
private BlogPostCardDto? _latestPost;
private UpdateCustomerProfileRequest _updateUserRequest = new();
private MudForm? _personalForm;
private bool _isPersonalSaving;
private bool _isSettingsSaving;
private string _copyMessage = string.Empty;
private DateTime? _date;
// Referral Link Control
private bool _hasPurchasedPackage;
private bool _isClubMemberActive;
private bool CanShowReferralLink => _hasPurchasedPackage && _isClubMemberActive;
// Purchase flow
private bool _isProcessingPayment;
private int _purchaseCycleCount; // تعداد دورهای خرید — اگر > 0 فقط IPG مجاز
// Dashboard Tiles
private readonly List<DashTile> _dashTiles = new()
{
new(RouteConstants.Profile.Personal, Icons.Material.Filled.Person, "اطلاعات شخصی", "نمایش و ویرایش", "background:rgba(99,102,241,.12); color:#6366f1;"),
new(RouteConstants.Profile.Addresses, Icons.Material.Filled.LocationOn, "آدرس‌ها", "مدیریت آدرس‌ها", "background:rgba(99,102,241,.12); color:#6366f1;"),
new(RouteConstants.Profile.Tree, Icons.Material.Filled.AccountTree, "سازمان فروش", "ساختار تیم", "background:rgba(99,102,241,.12); color:#6366f1;"),
new(RouteConstants.Gateway.StoreChooser, Icons.Material.Filled.Store, "فروشگاه‌ها", "ورود به فروشگاه", "background:rgba(16,185,129,.12); color:#10b981;"),
new(RouteConstants.Profile.Wallet, Icons.Material.Filled.AccountBalanceWallet, "کیف پول", "مدیریت و تاریخچه", "background:rgba(16,185,129,.12); color:#10b981;"),
new(RouteConstants.Profile.MagicWallet, Icons.Material.Filled.AutoAwesome, "کیف‌پول جادویی", "شارژ چند‌برابری", "background:rgba(168,85,247,.12); color:#a855f7;"),
new(RouteConstants.Profile.WithdrawalRequests, Icons.Material.Filled.RequestPage, "درخواست برداشت", "ثبت و پیگیری", "background:rgba(245,158,11,.12); color:#f59e0b;"),
new(RouteConstants.Club.Membership, Icons.Material.Filled.CardMembership, "باشگاه مشتریان", "عضویت و مزایا", "background:rgba(168,85,247,.12); color:#a855f7;"),
new(RouteConstants.Network.Statistics, Icons.Material.Filled.Groups, "آمار باشگاه", "جزئیات باشگاه", "background:rgba(59,130,246,.12); color:#3b82f6;"),
new(RouteConstants.Commission.Dashboard, Icons.Material.Filled.Payments, "پاداش", "درآمد و تاریخچه", "background:rgba(16,185,129,.12); color:#10b981;"),
};
private record DashTile(string Href, string Icon, string Title, string Subtitle, string AvatarStyle);
// Address management
private List<CustomerAddressModel> _addresses = new();
private bool _isLoadingAddresses;
private CreateNewUserAddressRequest _newAddressRequest = new();
private UpdateUserAddressRequest _editAddressRequest = new();
private MudForm? _addressForm;
private readonly CreateNewUserAddressRequestValidator _addressValidator = new();
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
// Refresh token to get latest user claims
await AuthService.RefreshTokenAsync();
await LoadUserAuthInfo();
await LoadUserProfile();
await LoadAddresses();
await LoadWallet();
await LoadLatestPost();
// نمایش Modal قرارداد باشگاه اگر پکیج خریده ولی باشگاه فعال نشده
await CheckAndShowClubContractModal();
}
}
private async Task LoadUserAuthInfo()
{
try
{
var userInfo = await AuthService.GetUserAuthInfo();
_hasPurchasedPackage = userInfo.HasPurchasedPackage;
_isClubMemberActive = userInfo.IsClubMemberActive;
}
catch
{
_hasPurchasedPackage = false;
_isClubMemberActive = false;
}
StateHasChanged();
}
/// <summary>
/// بررسی و نمایش Modal قرارداد باشگاه مشتریان
/// اگر کاربر پکیج خریده ولی هنوز قرارداد باشگاه را امضا نکرده
/// </summary>
private async Task CheckAndShowClubContractModal()
{
// اگر پکیج خریده ولی باشگاه فعال نیست = باید قرارداد امضا کند
if (_hasPurchasedPackage && !_isClubMemberActive)
{
var options = new DialogOptions
{
BackdropClick = false, // غیرقابل بسته شدن با کلیک بیرون
CloseOnEscapeKey = false, // غیرقابل بسته شدن با Escape
CloseButton = false, // بدون دکمه بستن
MaxWidth = MaxWidth.Medium,
FullWidth = true
};
var dialog = await DialogService.ShowAsync<Shared.ClubMembershipContractDialog>(
"قرارداد باشگاه مشتریان",
options);
var result = await dialog.Result;
// اگر موفق بود، صفحه را refresh کنیم
if (!result.Canceled)
{
await LoadUserAuthInfo();
StateHasChanged();
}
}
}
private string _walletCredit = "-";
private string _walletDiscount = "-";
private string _walletNetwork = "-";
private async Task LoadWallet()
{
var b = await WalletService.GetBalancesAsync();
_walletCredit = FormatPrice(b.CreditBalance);
var discount = FormatPrice(b.DiscountBalance);
_walletNetwork = FormatPrice(b.NetworkBalance);
_walletDiscount = discount;
// بارگذاری تعداد دورهای خرید برای کنترل روش‌های پرداخت
var magicStatus = await WalletService.GetMagicWalletStatusAsync();
_purchaseCycleCount = magicStatus.PurchaseCycleCount;
StateHasChanged();
}
private static string FormatPrice(long price) => string.Format("{0:N0} تومان", price);
private async Task LoadLatestPost()
{
try
{
var posts = await BlogPostService.GetFeaturedPostsAsync(1);
if (!posts.Any())
{
var result = await BlogPostService.GetPublishedPostsAsync(page: 1, pageSize: 1);
posts = result.Posts;
}
_latestPost = posts.FirstOrDefault();
}
catch
{
_latestPost = null;
}
StateHasChanged();
}
private async Task LoadUserProfile()
{
try
{
_userProfile = await UserContract.GetUserForCustomerAsync(new());
_updateUserRequest = new UpdateCustomerProfileRequest
{
FirstName = _userProfile.FirstName,
LastName = _userProfile.LastName,
Email = _userProfile.Email,
NationalCode = _userProfile.NationalCode,
};
if (_userProfile.BirthDate != null)
{
_date = _userProfile.BirthDate.ToDateTime();
_updateUserRequest.BirthDate = _userProfile.BirthDate;
}
}
catch (Exception ex)
{
// Handle the case when user is not authenticated or API fails
_userProfile = new GetUserForCustomerResponse();
}
StateHasChanged();
}
private async Task SavePersonalInfo()
{
if (_personalForm is null) return;
await _personalForm.Validate();
if (!_personalForm.IsValid) return;
_isPersonalSaving = true;
try
{
if (!string.IsNullOrWhiteSpace(_updateUserRequest.NationalCode))
_updateUserRequest.NationalCode = _updateUserRequest.NationalCode.PersianToEnglish();
if (_date != null)
_updateUserRequest.BirthDate = _date.Value.DateTimeToTimestamp();
await UserContract.UpdateCustomerProfileAsync(request: _updateUserRequest);
await LoadUserProfile();
Snackbar.Add("اطلاعات شخصی با موفقیت ذخیره شد.", Severity.Success);
}
catch (Exception ex)
{
Snackbar.Add($"خطا در ذخیره اطلاعات: {ex.Message}", Severity.Error);
}
finally
{
_isPersonalSaving = false;
await InvokeAsync(StateHasChanged);
}
}
private async Task SaveSettings()
{
_isSettingsSaving = true;
try
{
await SavePersonalInfo();
}
catch (Exception ex)
{
Snackbar.Add($"خطا در ذخیره تنظیمات: {ex.Message}", Severity.Error);
}
finally
{
_isSettingsSaving = false;
await InvokeAsync(StateHasChanged);
}
}
private async Task CopyReferralCode()
{
try
{
await JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", $"{Navigation.BaseUri}?ref={_userProfile.ReferralCode}");
_copyMessage = "کد دعوت کپی شد!";
Snackbar.Add("کد دعوت در کلیپ‌بورد کپی شد.", Severity.Success);
// Clear message after 3 seconds
await Task.Delay(3000);
_copyMessage = string.Empty;
await InvokeAsync(StateHasChanged);
}
catch (Exception ex)
{
Snackbar.Add("خطا در کپی کردن کد دعوت.", Severity.Error);
}
}
private async Task ShareReferralCode()
{
var shareText = $"کد دعوت من در کارا بازار سلامت: {_userProfile.ReferralCode}\nبرای عضویت از این لینک استفاده کنید:";
try
{
// Try to use Web Share API if available
await JSRuntime.InvokeVoidAsync("navigator.share",
new
{
title = "کد دعوت کارا بازار سلامت",
text = shareText,
url = $"{Navigation.BaseUri}?ref={_userProfile.ReferralCode}"
});
}
catch
{
// Fallback: copy to clipboard
await JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", $"{Navigation.BaseUri}?ref={_userProfile.ReferralCode}");
Snackbar.Add("لینک دعوت در کلیپ‌بورد کپی شد.", Severity.Success);
}
}
// Address management methods
private async Task LoadAddresses()
{
_isLoadingAddresses = true;
try
{
var response = await UserAddressContract.GetCustomerAddressesAsync(request: new());
if (response?.Models?.Any() == true)
{
_addresses = response.Models.ToList();
}
else
{
_addresses = new List<CustomerAddressModel>();
}
}
catch (Exception ex)
{
Snackbar.Add($"خطا در بارگذاری آدرس‌ها: {ex.Message}", Severity.Error);
_addresses = new List<CustomerAddressModel>();
}
finally
{
_isLoadingAddresses = false;
await InvokeAsync(StateHasChanged);
}
}
private async Task OpenAddAddressDialog()
{
var dialog = await DialogService.ShowAsync<AddAddressDialog>("افزودن آدرس جدید");
var result = await dialog.Result;
if (!result.Canceled)
{
await LoadAddresses();
await AuthService.RefreshTokenAsync();
}
}
private async Task OpenEditAddressDialog(CustomerAddressModel address)
{
var dialog = await DialogService.ShowAsync<EditAddressDialog>("ویرایش آدرس", new DialogParameters<EditAddressDialog>
{
{ x => x.Model, address }
});
var result = await dialog.Result;
if (!result.Canceled)
{
await LoadAddresses();
}
}
private async Task SetAsDefaultAddress(long addressId)
{
try
{
await UserAddressContract.SetAddressAsDefaultAsync(request: new()
{
Id = addressId
});
Snackbar.Add("آدرس پیش‌فرض با موفقیت تغییر کرد.", Severity.Success);
await LoadAddresses();
}
catch (Exception ex)
{
Snackbar.Add($"خطا در تغییر آدرس پیش‌فرض: {ex.Message}", Severity.Error);
}
}
private async Task DeleteAddress(long addressId)
{
var result = await DialogService.ShowMessageBox(
"تأیید حذف",
"آیا از حذف این آدرس اطمینان دارید؟",
yesText: "حذف",
cancelText: "لغو");
if (result == true)
{
try
{
await UserAddressContract.DeleteUserAddressAsync(request: new()
{
Id = addressId
});
Snackbar.Add("آدرس با موفقیت حذف شد.", Severity.Success);
await LoadAddresses();
await AuthService.RefreshTokenAsync();
}
catch (Exception ex)
{
Snackbar.Add($"خطا در حذف آدرس: {ex.Message}", Severity.Error);
}
}
}
private async Task OpenPurchaseOptions()
{
var options = new DialogOptions
{
MaxWidth = MaxWidth.Small,
FullWidth = true,
CloseOnEscapeKey = true,
BackdropClick = true
};
var parameters = new DialogParameters<Shared.PackagePurchaseDialog>
{
{ x => x.PurchaseCycleCount, _purchaseCycleCount }
};
var dialog = await DialogService.ShowAsync<Shared.PackagePurchaseDialog>(
string.Empty, parameters, options);
var result = await dialog.Result;
if (result.Canceled) return;
if (result.Data is Shared.PackagePurchaseDialog.PackagePurchaseResult purchase)
{
switch (purchase.Method)
{
case Shared.PackagePurchaseDialog.PaymentMethodType.DirectPayment:
await ProcessDirectPayment(purchase.Package.Id, gatewayProvider: null);
break;
case Shared.PackagePurchaseDialog.PaymentMethodType.TwentyCard:
await ProcessTwentyCardPayment(purchase.Package.Id);
break;
case Shared.PackagePurchaseDialog.PaymentMethodType.DayaLoan:
await ProcessDayaLoanPayment();
break;
}
}
}
private async Task ProcessTwentyCardPayment(long packageId)
{
var options = new DialogOptions
{
MaxWidth = MaxWidth.Small,
FullWidth = true,
CloseOnEscapeKey = true,
BackdropClick = true
};
var dialog = await DialogService.ShowAsync<Shared.TwentyCardCreditDialog>(
string.Empty, options);
var result = await dialog.Result;
if (result.Canceled) return;
await ProcessDirectPayment(packageId, gatewayProvider: "20card");
}
private async Task ProcessDirectPayment(long packageId, string? gatewayProvider = null)
{
if (_isProcessingPayment) return;
_isProcessingPayment = true;
StateHasChanged();
try
{
var request = new CustomerPurchasePackageRequest
{
PackageId = packageId,
PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway
};
if (!string.IsNullOrWhiteSpace(gatewayProvider))
request.GatewayProvider = gatewayProvider;
var response = await PackageContract.CustomerPurchasePackageAsync(request);
if (!response.Success)
{
Snackbar.Add(response.Message ?? "خطا در آغاز فرآیند پرداخت", Severity.Error);
return;
}
if (!string.IsNullOrEmpty(response.PaymentGatewayUrl))
{
Navigation.NavigateTo(response.PaymentGatewayUrl, forceLoad: true);
}
else
{
Snackbar.Add("خطا در دریافت آدرس درگاه پرداخت", Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add($"خطا در فرآیند پرداخت: {ex.Message}", Severity.Error);
}
finally
{
_isProcessingPayment = false;
await InvokeAsync(StateHasChanged);
}
}
private async Task ProcessDayaLoanPayment()
{
if (string.IsNullOrWhiteSpace(_updateUserRequest.FirstName))
{
Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (نام وارد نشده)", Severity.Error);
return;
}
if (string.IsNullOrWhiteSpace(_updateUserRequest.LastName))
{
Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (نام خانوادگی وارد نشده)", Severity.Error);
return;
}
if (string.IsNullOrWhiteSpace(_updateUserRequest.NationalCode))
{
Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (کدملی وارد نشده)", Severity.Error);
return;
}
if (_updateUserRequest.BirthDate == null)
{
Snackbar.Add("لطفا اطلاعات شخصی خود را تکمیل کنید. (تاریخ تولد وارد نشده)", Severity.Error);
return;
}
var url = "https://dayadiamond.ir/profile/creditpurchase/?merchantcode=56146364";
await JSRuntime.InvokeVoidAsync("open", url, "_blank");
}
}