feat(payment): integrate TwentyCard payment option and enhance checkout flow
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 5m6s

- 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.
This commit is contained in:
masoodafar-web
2026-09-20 22:17:39 +03:30
parent 8252e408b6
commit aedf312812
12 changed files with 178 additions and 14 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
# Keep production config from current branch during merges — never overwrite # Keep production config from current branch during merges — never ov erwrite
src/FrontOffice.Main/appsettings.Production.json merge=ours src/FrontOffice.Main/appsettings.Production.json merge=ours
+1 -1
View File
@@ -1,6 +1,6 @@
# ---> VisualStudio # ---> VisualStudio
## Ignore Visual Studio temporary files, build results, and ## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons. ## files generated by popular Visual Studio add-ons.
## ##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+6 -2
View File
@@ -10,8 +10,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 (CI/CD packs → Nexus) --> <!-- Local protobuf until 0.0.212 is published to Nexus -->
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.211" /> <!-- <PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.212" /> -->
<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" />
@@ -23,6 +23,10 @@
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\CMS\src\CMSMicroservice.Protobuf\CMSMicroservice.Protobuf.csproj" />
</ItemGroup>
<!-- Local Proto Project Reference for development --> <!-- Local Proto Project Reference for development -->
<!-- <ItemGroup>--> <!-- <ItemGroup>-->
<!-- <ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.Package.Protobuf\FrontOffice.BFF.Package.Protobuf.csproj" />--> <!-- <ProjectReference Include="..\..\..\FrontOffice.BFF\src\Protobufs\FrontOffice.BFF.Package.Protobuf\FrontOffice.BFF.Package.Protobuf.csproj" />-->
+12 -1
View File
@@ -140,7 +140,18 @@
OnClick="ProcessPayment" OnClick="ProcessPayment"
Disabled="@(!CanProceedToPayment || _isProcessingPayment || !_selectedPackage.SupportsDirectPurchase)" Disabled="@(!CanProceedToPayment || _isProcessingPayment || !_selectedPackage.SupportsDirectPurchase)"
Class="mt-2"> Class="mt-2">
@(_isProcessingPayment ? "در حال پردازش..." : "پرداخت آنلاین") @(_isProcessingPayment ? "در حال پردازش..." : "پرداخت آنلاین (زرین‌پال)")
</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Info"
Size="Size.Large"
FullWidth="true"
StartIcon="@Icons.Material.Filled.AccountBalance"
OnClick="ProcessTwentyCardPayment"
Disabled="@(!CanProceedToPayment || _isProcessingPayment || !_selectedPackage.SupportsDirectPurchase)"
Class="mt-2">
پرداخت با ۲۰کارت
</MudButton> </MudButton>
@if (_selectedPackage.SupportsDayaPurchase) @if (_selectedPackage.SupportsDayaPurchase)
+33 -2
View File
@@ -107,6 +107,33 @@ public partial class Checkout
} }
private async Task ProcessPayment() private async Task ProcessPayment()
=> await StartGatewayPayment(gatewayProvider: null);
private async Task ProcessTwentyCardPayment()
{
if (!CanProceedToPayment || _selectedPackage == null)
{
Snackbar.Add("لطفاً پکیج را انتخاب کنید.", Severity.Warning);
return;
}
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 StartGatewayPayment(gatewayProvider: "20card");
}
private async Task StartGatewayPayment(string? gatewayProvider)
{ {
if (_isProcessingPayment) return; if (_isProcessingPayment) return;
@@ -120,11 +147,15 @@ public partial class Checkout
try try
{ {
var response = await PackageClient.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest var request = new CustomerPurchasePackageRequest
{ {
PackageId = _selectedPackage.Id, PackageId = _selectedPackage.Id,
PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway
}); };
if (!string.IsNullOrWhiteSpace(gatewayProvider))
request.GatewayProvider = gatewayProvider;
var response = await PackageClient.CustomerPurchasePackageAsync(request);
if (!response.Success) if (!response.Success)
{ {
@@ -421,7 +421,10 @@ public partial class Index
switch (purchase.Method) switch (purchase.Method)
{ {
case Shared.PackagePurchaseDialog.PaymentMethodType.DirectPayment: case Shared.PackagePurchaseDialog.PaymentMethodType.DirectPayment:
await ProcessDirectPayment(purchase.Package.Id); await ProcessDirectPayment(purchase.Package.Id, gatewayProvider: null);
break;
case Shared.PackagePurchaseDialog.PaymentMethodType.TwentyCard:
await ProcessTwentyCardPayment(purchase.Package.Id);
break; break;
case Shared.PackagePurchaseDialog.PaymentMethodType.DayaLoan: case Shared.PackagePurchaseDialog.PaymentMethodType.DayaLoan:
await ProcessDayaLoanPayment(); await ProcessDayaLoanPayment();
@@ -430,7 +433,25 @@ public partial class Index
} }
} }
private async Task ProcessDirectPayment(long packageId) 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; if (_isProcessingPayment) return;
@@ -439,11 +460,15 @@ public partial class Index
try try
{ {
var response = await PackageContract.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest var request = new CustomerPurchasePackageRequest
{ {
PackageId = packageId, PackageId = packageId,
PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway
}); };
if (!string.IsNullOrWhiteSpace(gatewayProvider))
request.GatewayProvider = gatewayProvider;
var response = await PackageContract.CustomerPurchasePackageAsync(request);
if (!response.Success) if (!response.Success)
{ {
@@ -85,6 +85,10 @@
[SupplyParameterFromQuery(Name = "Status")] [SupplyParameterFromQuery(Name = "Status")]
private string? Status { get; set; } private string? Status { get; set; }
/// <summary>۲۰کارت callback — هم‌معنی Status زرین‌پال</summary>
[SupplyParameterFromQuery(Name = "PaymentStatus")]
private string? PaymentStatus { get; set; }
private bool _isLoading = true; private bool _isLoading = true;
private bool _isSuccess; private bool _isSuccess;
@@ -132,6 +136,10 @@
{ {
try try
{ {
// ۲۰کارت: PaymentStatus؛ زرین‌پال: Status
if (string.IsNullOrWhiteSpace(Status) && !string.IsNullOrWhiteSpace(PaymentStatus))
Status = PaymentStatus;
// تعیین نوع پرداخت — اگر type نباشد، پیش‌فرض خرید پکیج // تعیین نوع پرداخت — اگر type نباشد، پیش‌فرض خرید پکیج
var type = PaymentType?.ToLowerInvariant() ?? "package"; var type = PaymentType?.ToLowerInvariant() ?? "package";
@@ -52,7 +52,7 @@
روش پرداخت خود را انتخاب کنید: روش پرداخت خود را انتخاب کنید:
</MudText> </MudText>
@* Direct Payment — always available *@ @* Direct Payment (ZarinPal) — always available *@
<MudPaper Elevation="0" Class="pa-4 rounded-lg pkg-payment-option" <MudPaper Elevation="0" Class="pa-4 rounded-lg pkg-payment-option"
Style="border:2px solid var(--mud-palette-primary); cursor:pointer;" Style="border:2px solid var(--mud-palette-primary); cursor:pointer;"
@onclick="() => SelectPaymentMethod(PaymentMethodType.DirectPayment)"> @onclick="() => SelectPaymentMethod(PaymentMethodType.DirectPayment)">
@@ -61,13 +61,29 @@
<MudIcon Icon="@Icons.Material.Filled.CreditCard" Color="Color.Primary" Size="Size.Medium" /> <MudIcon Icon="@Icons.Material.Filled.CreditCard" Color="Color.Primary" Size="Size.Medium" />
<MudStack Spacing="0" Class="flex-grow-1"> <MudStack Spacing="0" Class="flex-grow-1">
<MudText Typo="Typo.subtitle1" Color="Color.Primary"><b>پرداخت مستقیم</b></MudText> <MudText Typo="Typo.subtitle1" Color="Color.Primary"><b>پرداخت مستقیم</b></MudText>
<MudText Typo="Typo.caption" Class="mud-text-secondary">پرداخت آنی از طریق درگاه بانکی</MudText> <MudText Typo="Typo.caption" Class="mud-text-secondary">پرداخت آنی از طریق درگاه بانکی (زرین‌پال)</MudText>
</MudStack> </MudStack>
<MudIcon Icon="@Icons.Material.Filled.ArrowBack" Color="Color.Primary" Size="Size.Small" /> <MudIcon Icon="@Icons.Material.Filled.ArrowBack" Color="Color.Primary" Size="Size.Small" />
</MudStack> </MudStack>
</MudStack> </MudStack>
</MudPaper> </MudPaper>
@* TwentyCard IPG *@
<MudPaper Elevation="0" Class="pa-4 rounded-lg pkg-payment-option"
Style="border:2px solid var(--mud-palette-info); cursor:pointer;"
@onclick="() => SelectPaymentMethod(PaymentMethodType.TwentyCard)">
<MudStack Spacing="2">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.AccountBalance" Color="Color.Info" Size="Size.Medium" />
<MudStack Spacing="0" Class="flex-grow-1">
<MudText Typo="Typo.subtitle1" Color="Color.Info"><b>پرداخت با ۲۰کارت</b></MudText>
<MudText Typo="Typo.caption" Class="mud-text-secondary">پرداخت از طریق درگاه ۲۰کارت (نیاز به تأمین اعتبار)</MudText>
</MudStack>
<MudIcon Icon="@Icons.Material.Filled.ArrowBack" Color="Color.Info" Size="Size.Small" />
</MudStack>
</MudStack>
</MudPaper>
@* Daya Loan — only for base package AND first purchase cycle *@ @* Daya Loan — only for base package AND first purchase cycle *@
@if (_selectedPackage.SupportsDayaPurchase && PurchaseCycleCount == 0) @if (_selectedPackage.SupportsDayaPurchase && PurchaseCycleCount == 0)
{ {
@@ -229,6 +245,6 @@
: "background:rgba(16,185,129,.12); color:#10b981;"; : "background:rgba(16,185,129,.12); color:#10b981;";
} }
public enum PaymentMethodType { DirectPayment, DayaLoan } public enum PaymentMethodType { DirectPayment, TwentyCard, DayaLoan }
public record PackagePurchaseResult(PackageDto Package, PaymentMethodType Method); public record PackagePurchaseResult(PackageDto Package, PaymentMethodType Method);
} }
@@ -0,0 +1,60 @@
@inject IConfiguration Configuration
@inject IJSRuntime JSRuntime
<MudDialog>
<TitleContent>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.AccountBalance" Color="Color.Primary" />
<MudText Typo="Typo.h6">تأمین اعتبار ۲۰کارت</MudText>
</MudStack>
</TitleContent>
<DialogContent>
<MudStack Spacing="3">
<MudText Typo="Typo.body2" Class="mud-text-secondary">
برای پرداخت از طریق درگاه ۲۰کارت، ابتدا باید اعتبار حساب ۲۰کارت خود را تأمین کنید.
پس از تأمین اعتبار، روی «ادامه پرداخت» بزنید تا به درگاه هدایت شوید.
</MudText>
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined">
اگر قبلاً اعتبار را تأمین کرده‌اید، می‌توانید مستقیم ادامه دهید.
</MudAlert>
</MudStack>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel" Variant="Variant.Text">انصراف</MudButton>
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.OpenInNew"
OnClick="OpenCreditPortal"
Disabled="@string.IsNullOrWhiteSpace(_creditPortalUrl)">
تأمین اعتبار
</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Payment"
OnClick="ContinuePayment">
ادامه پرداخت
</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = default!;
private string _creditPortalUrl = string.Empty;
protected override void OnInitialized()
{
_creditPortalUrl = Configuration["TwentyCard:CreditPortalUrl"] ?? string.Empty;
}
private async Task OpenCreditPortal()
{
if (string.IsNullOrWhiteSpace(_creditPortalUrl)) return;
await JSRuntime.InvokeVoidAsync("open", _creditPortalUrl, "_blank");
}
private void ContinuePayment() => MudDialog.Close(DialogResult.Ok(true));
private void Cancel() => MudDialog.Cancel();
}
@@ -1,6 +1,9 @@
{ {
"GwUrl": "https://cms.kbs1.ir", "GwUrl": "https://cms.kbs1.ir",
"DownloadUrl": "", "DownloadUrl": "",
"TwentyCard": {
"CreditPortalUrl": "https://20card.ir"
},
"EncryptionSettings": { "EncryptionSettings": {
"Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=", "Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=",
"IV": "1wyF3Tt142MOkCpIyCxh/g==" "IV": "1wyF3Tt142MOkCpIyCxh/g=="
@@ -6,6 +6,9 @@
}, },
"GwUrl": "https://cms.se.kbs1.ir", "GwUrl": "https://cms.se.kbs1.ir",
"DownloadUrl": "", "DownloadUrl": "",
"TwentyCard": {
"CreditPortalUrl": "https://20card.ir"
},
"EncryptionSettings": { "EncryptionSettings": {
"Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=", "Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=",
"IV": "1wyF3Tt142MOkCpIyCxh/g==" "IV": "1wyF3Tt142MOkCpIyCxh/g=="
+3
View File
@@ -6,6 +6,9 @@
}, },
"GwUrl": "https://cms.se.kbs1.ir", "GwUrl": "https://cms.se.kbs1.ir",
"DownloadUrl": "", "DownloadUrl": "",
"TwentyCard": {
"CreditPortalUrl": "https://20card.ir"
},
"EncryptionSettings": { "EncryptionSettings": {
"Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=", "Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=",
"IV": "1wyF3Tt142MOkCpIyCxh/g==" "IV": "1wyF3Tt142MOkCpIyCxh/g=="