diff --git a/.gitattributes b/.gitattributes
index 82b5185..679f26c 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -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
diff --git a/.gitignore b/.gitignore
index 0f13c79..28a74b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
# ---> VisualStudio
## 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
diff --git a/src/FrontOffice.Main/FrontOffice.Main.csproj b/src/FrontOffice.Main/FrontOffice.Main.csproj
index 8d8f9e7..eac8e04 100644
--- a/src/FrontOffice.Main/FrontOffice.Main.csproj
+++ b/src/FrontOffice.Main/FrontOffice.Main.csproj
@@ -10,8 +10,8 @@
-
-
+
+
@@ -23,6 +23,10 @@
+
+
+
+
diff --git a/src/FrontOffice.Main/Pages/Checkout.razor b/src/FrontOffice.Main/Pages/Checkout.razor
index f138e5b..c268589 100644
--- a/src/FrontOffice.Main/Pages/Checkout.razor
+++ b/src/FrontOffice.Main/Pages/Checkout.razor
@@ -140,7 +140,18 @@
OnClick="ProcessPayment"
Disabled="@(!CanProceedToPayment || _isProcessingPayment || !_selectedPackage.SupportsDirectPurchase)"
Class="mt-2">
- @(_isProcessingPayment ? "در حال پردازش..." : "پرداخت آنلاین")
+ @(_isProcessingPayment ? "در حال پردازش..." : "پرداخت آنلاین (زرینپال)")
+
+
+
+ پرداخت با ۲۰کارت
@if (_selectedPackage.SupportsDayaPurchase)
diff --git a/src/FrontOffice.Main/Pages/Checkout.razor.cs b/src/FrontOffice.Main/Pages/Checkout.razor.cs
index 5e30af5..a15bf17 100644
--- a/src/FrontOffice.Main/Pages/Checkout.razor.cs
+++ b/src/FrontOffice.Main/Pages/Checkout.razor.cs
@@ -107,6 +107,33 @@ public partial class Checkout
}
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(
+ 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;
@@ -120,11 +147,15 @@ public partial class Checkout
try
{
- var response = await PackageClient.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest
+ var request = new CustomerPurchasePackageRequest
{
PackageId = _selectedPackage.Id,
PurchaseMethod = PurchaseMethodEnum.PurchaseMethodGateway
- });
+ };
+ if (!string.IsNullOrWhiteSpace(gatewayProvider))
+ request.GatewayProvider = gatewayProvider;
+
+ var response = await PackageClient.CustomerPurchasePackageAsync(request);
if (!response.Success)
{
diff --git a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs
index 2f4a266..09708a7 100644
--- a/src/FrontOffice.Main/Pages/Profile/Index.razor.cs
+++ b/src/FrontOffice.Main/Pages/Profile/Index.razor.cs
@@ -421,7 +421,10 @@ public partial class Index
switch (purchase.Method)
{
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;
case Shared.PackagePurchaseDialog.PaymentMethodType.DayaLoan:
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(
+ 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;
@@ -439,11 +460,15 @@ public partial class Index
try
{
- var response = await PackageContract.CustomerPurchasePackageAsync(new CustomerPurchasePackageRequest
+ 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)
{
diff --git a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor
index c70178f..3ca2632 100644
--- a/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor
+++ b/src/FrontOffice.Main/Pages/Profile/PaymentCallback.razor
@@ -85,6 +85,10 @@
[SupplyParameterFromQuery(Name = "Status")]
private string? Status { get; set; }
+
+ /// ۲۰کارت callback — هممعنی Status زرینپال
+ [SupplyParameterFromQuery(Name = "PaymentStatus")]
+ private string? PaymentStatus { get; set; }
private bool _isLoading = true;
private bool _isSuccess;
@@ -132,6 +136,10 @@
{
try
{
+ // ۲۰کارت: PaymentStatus؛ زرینپال: Status
+ if (string.IsNullOrWhiteSpace(Status) && !string.IsNullOrWhiteSpace(PaymentStatus))
+ Status = PaymentStatus;
+
// تعیین نوع پرداخت — اگر type نباشد، پیشفرض خرید پکیج
var type = PaymentType?.ToLowerInvariant() ?? "package";
diff --git a/src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor b/src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor
index c997ff1..5230825 100644
--- a/src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor
+++ b/src/FrontOffice.Main/Shared/PackagePurchaseDialog.razor
@@ -52,7 +52,7 @@
روش پرداخت خود را انتخاب کنید:
- @* Direct Payment — always available *@
+ @* Direct Payment (ZarinPal) — always available *@
SelectPaymentMethod(PaymentMethodType.DirectPayment)">
@@ -61,13 +61,29 @@
پرداخت مستقیم
- پرداخت آنی از طریق درگاه بانکی
+ پرداخت آنی از طریق درگاه بانکی (زرینپال)
+ @* TwentyCard IPG *@
+ SelectPaymentMethod(PaymentMethodType.TwentyCard)">
+
+
+
+
+ پرداخت با ۲۰کارت
+ پرداخت از طریق درگاه ۲۰کارت (نیاز به تأمین اعتبار)
+
+
+
+
+
+
@* Daya Loan — only for base package AND first purchase cycle *@
@if (_selectedPackage.SupportsDayaPurchase && PurchaseCycleCount == 0)
{
@@ -229,6 +245,6 @@
: "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);
}
diff --git a/src/FrontOffice.Main/Shared/TwentyCardCreditDialog.razor b/src/FrontOffice.Main/Shared/TwentyCardCreditDialog.razor
new file mode 100644
index 0000000..1f9b13f
--- /dev/null
+++ b/src/FrontOffice.Main/Shared/TwentyCardCreditDialog.razor
@@ -0,0 +1,60 @@
+@inject IConfiguration Configuration
+@inject IJSRuntime JSRuntime
+
+
+
+
+
+ تأمین اعتبار ۲۰کارت
+
+
+
+
+
+ برای پرداخت از طریق درگاه ۲۰کارت، ابتدا باید اعتبار حساب ۲۰کارت خود را تأمین کنید.
+ پس از تأمین اعتبار، روی «ادامه پرداخت» بزنید تا به درگاه هدایت شوید.
+
+
+
+ اگر قبلاً اعتبار را تأمین کردهاید، میتوانید مستقیم ادامه دهید.
+
+
+
+
+ انصراف
+
+ تأمین اعتبار
+
+
+ ادامه پرداخت
+
+
+
+
+@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();
+}
diff --git a/src/FrontOffice.Main/appsettings.Production.json b/src/FrontOffice.Main/appsettings.Production.json
index 6f08557..3411fe9 100644
--- a/src/FrontOffice.Main/appsettings.Production.json
+++ b/src/FrontOffice.Main/appsettings.Production.json
@@ -1,6 +1,9 @@
{
"GwUrl": "https://cms.kbs1.ir",
"DownloadUrl": "",
+ "TwentyCard": {
+ "CreditPortalUrl": "https://20card.ir"
+ },
"EncryptionSettings": {
"Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=",
"IV": "1wyF3Tt142MOkCpIyCxh/g=="
diff --git a/src/FrontOffice.Main/appsettings.Staging.json b/src/FrontOffice.Main/appsettings.Staging.json
index f07c714..038c752 100644
--- a/src/FrontOffice.Main/appsettings.Staging.json
+++ b/src/FrontOffice.Main/appsettings.Staging.json
@@ -6,6 +6,9 @@
},
"GwUrl": "https://cms.se.kbs1.ir",
"DownloadUrl": "",
+ "TwentyCard": {
+ "CreditPortalUrl": "https://20card.ir"
+ },
"EncryptionSettings": {
"Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=",
"IV": "1wyF3Tt142MOkCpIyCxh/g=="
diff --git a/src/FrontOffice.Main/appsettings.json b/src/FrontOffice.Main/appsettings.json
index a8b7510..9ecc7ab 100644
--- a/src/FrontOffice.Main/appsettings.json
+++ b/src/FrontOffice.Main/appsettings.json
@@ -6,6 +6,9 @@
},
"GwUrl": "https://cms.se.kbs1.ir",
"DownloadUrl": "",
+ "TwentyCard": {
+ "CreditPortalUrl": "https://20card.ir"
+ },
"EncryptionSettings": {
"Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=",
"IV": "1wyF3Tt142MOkCpIyCxh/g=="