From aedf31281263384013b7f89c9792d8d1436bd055 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sun, 20 Sep 2026 22:17:39 +0330 Subject: [PATCH] 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. --- .gitattributes | 2 +- .gitignore | 2 +- src/FrontOffice.Main/FrontOffice.Main.csproj | 8 ++- src/FrontOffice.Main/Pages/Checkout.razor | 13 +++- src/FrontOffice.Main/Pages/Checkout.razor.cs | 35 ++++++++++- .../Pages/Profile/Index.razor.cs | 33 ++++++++-- .../Pages/Profile/PaymentCallback.razor | 8 +++ .../Shared/PackagePurchaseDialog.razor | 22 ++++++- .../Shared/TwentyCardCreditDialog.razor | 60 +++++++++++++++++++ .../appsettings.Production.json | 3 + src/FrontOffice.Main/appsettings.Staging.json | 3 + src/FrontOffice.Main/appsettings.json | 3 + 12 files changed, 178 insertions(+), 14 deletions(-) create mode 100644 src/FrontOffice.Main/Shared/TwentyCardCreditDialog.razor 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 *@ @@ -61,13 +61,29 @@ پرداخت مستقیم - پرداخت آنی از طریق درگاه بانکی + پرداخت آنی از طریق درگاه بانکی (زرین‌پال) + @* TwentyCard IPG *@ + + + + + + پرداخت با ۲۰کارت + پرداخت از طریق درگاه ۲۰کارت (نیاز به تأمین اعتبار) + + + + + + @* 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=="