diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f441c7fa..3586eeea 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,7 +22,7 @@ steps: displayName: Use Dotnet inputs: packageType: sdk - version: 8.x + version: 9.x - task: NuGetCommand@2 displayName: 'restoring' inputs: diff --git a/src/Parbad.AspNetCore/src/Parbad.AspNetCore.csproj b/src/Parbad.AspNetCore/src/Parbad.AspNetCore.csproj index b33400c5..187016de 100644 --- a/src/Parbad.AspNetCore/src/Parbad.AspNetCore.csproj +++ b/src/Parbad.AspNetCore/src/Parbad.AspNetCore.csproj @@ -3,8 +3,8 @@ Parbad.AspNetCore Parbad.AspNetCore - 1.4.0 - netstandard2.0;netstandard2.1;netcoreapp3.0;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + 1.5.0 + netstandard2.0;netstandard2.1;netcoreapp3.0;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0 latest Sina Soltani Copyright © Sina Soltani 2016 @@ -20,7 +20,7 @@ For more information see: https://www.nuget.org/packages/Parbad/ parbad aspnetcore Payment Gateway Bank Iran Shetab IranKish Mellat Melli Sadad Parsian Pasargad Saman Asan-Pardakht پرداخت درگاه بانک ایران شتاب ایران-کیش ملت ملی سداد پارسیان پاسارگاد سامان آسان-پرداخت true LGPL-3.0-or-later - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 @@ -37,8 +37,8 @@ For more information see: https://www.nuget.org/packages/Parbad/ - - + + diff --git a/src/Parbad.Gateway/FanAva/src/Parbad.Gateway.FanAva.csproj b/src/Parbad.Gateway/FanAva/src/Parbad.Gateway.FanAva.csproj index 8c228ea7..57bdc507 100644 --- a/src/Parbad.Gateway/FanAva/src/Parbad.Gateway.FanAva.csproj +++ b/src/Parbad.Gateway/FanAva/src/Parbad.Gateway.FanAva.csproj @@ -2,8 +2,8 @@ Parbad.Gateway.FanAva - netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0 - 1.2.0 + netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0;net9.0 + 1.3.0 latest Mohammad Mokhtari Copyright © Sina Soltani 2016 @@ -15,7 +15,7 @@ true parbad Payment Gateway FanAva پرداخت درگاه بانک فن آوا true - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 FanAva Gateway for Parbad project. For more information see: https://www.nuget.org/packages/Parbad/ diff --git a/src/Parbad.Gateway/FanAva/tests/Parbad.Gateway.FanAva.Tests.csproj b/src/Parbad.Gateway/FanAva/tests/Parbad.Gateway.FanAva.Tests.csproj index d20dd8c4..c78f7f05 100644 --- a/src/Parbad.Gateway/FanAva/tests/Parbad.Gateway.FanAva.Tests.csproj +++ b/src/Parbad.Gateway/FanAva/tests/Parbad.Gateway.FanAva.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 latest false diff --git a/src/Parbad.Gateway/IdPay/src/Parbad.Gateway.IdPay.csproj b/src/Parbad.Gateway/IdPay/src/Parbad.Gateway.IdPay.csproj index 01439de0..34fb5c52 100644 --- a/src/Parbad.Gateway/IdPay/src/Parbad.Gateway.IdPay.csproj +++ b/src/Parbad.Gateway/IdPay/src/Parbad.Gateway.IdPay.csproj @@ -1,8 +1,8 @@ - netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0 - 1.4.0 + netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0;net9.0 + 1.5.0 latest Sina Soltani Copyright © Sina Soltani 2016 @@ -14,7 +14,7 @@ true parbad Payment Gateway Bank Iran Shetab idpay پرداخت درگاه بانک ایران شتاب true - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 IDPay.ir Gateway for Parbad project. For more information see: https://www.nuget.org/packages/Parbad/ diff --git a/src/Parbad.Gateway/IdPay/tests/Parbad.Gateway.IdPay.Tests.csproj b/src/Parbad.Gateway/IdPay/tests/Parbad.Gateway.IdPay.Tests.csproj index a9a78dd3..45d35399 100644 --- a/src/Parbad.Gateway/IdPay/tests/Parbad.Gateway.IdPay.Tests.csproj +++ b/src/Parbad.Gateway/IdPay/tests/Parbad.Gateway.IdPay.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 latest false diff --git a/src/Parbad.Gateway/IranKish/src/Parbad.Gateway.IranKish.csproj b/src/Parbad.Gateway/IranKish/src/Parbad.Gateway.IranKish.csproj index 8d0b7a00..1478cc29 100644 --- a/src/Parbad.Gateway/IranKish/src/Parbad.Gateway.IranKish.csproj +++ b/src/Parbad.Gateway/IranKish/src/Parbad.Gateway.IranKish.csproj @@ -3,8 +3,8 @@ Parbad.Gateway.IranKish Parbad.Gateway.IranKish - 1.1.0 - netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0 + 1.2.0 + netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0;net9.0 latest Sina Soltani Copyright © Sina Soltani 2016 @@ -16,7 +16,7 @@ true parbad Payment gateway bank Iran shetab irankish پرداخت درگاه بانک ایران کیش true - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 IranKish Gateway for Parbad project. For more information see: https://www.nuget.org/packages/Parbad/ diff --git a/src/Parbad.Gateway/IranKish/tests/Parbad.Gateway.IranKish.Tests.csproj b/src/Parbad.Gateway/IranKish/tests/Parbad.Gateway.IranKish.Tests.csproj index de39da7c..b8c73ac9 100644 --- a/src/Parbad.Gateway/IranKish/tests/Parbad.Gateway.IranKish.Tests.csproj +++ b/src/Parbad.Gateway/IranKish/tests/Parbad.Gateway.IranKish.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 latest enable enable diff --git a/src/Parbad.Gateway/PayIr/src/Parbad.Gateway.PayIr.csproj b/src/Parbad.Gateway/PayIr/src/Parbad.Gateway.PayIr.csproj index 3fbe9a4b..7d14a403 100644 --- a/src/Parbad.Gateway/PayIr/src/Parbad.Gateway.PayIr.csproj +++ b/src/Parbad.Gateway/PayIr/src/Parbad.Gateway.PayIr.csproj @@ -3,8 +3,8 @@ Parbad.Gateway.PayIr Parbad.Gateway.PayIr - 1.6.0 - netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0 + 1.7.0 + netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0;net9.0 latest Sina Soltani Copyright © Sina Soltani 2016 @@ -16,7 +16,7 @@ true parbad Payment Gateway Bank Iran Shetab pay.ir پرداخت درگاه بانک ایران شتاب پی true - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 Pay.ir Gateway for Parbad project. For more information see: https://www.nuget.org/packages/Parbad/ diff --git a/src/Parbad.Gateway/PayPing/src/Parbad.Gateway.PayPing.csproj b/src/Parbad.Gateway/PayPing/src/Parbad.Gateway.PayPing.csproj index 6719961c..3bab81b6 100644 --- a/src/Parbad.Gateway/PayPing/src/Parbad.Gateway.PayPing.csproj +++ b/src/Parbad.Gateway/PayPing/src/Parbad.Gateway.PayPing.csproj @@ -2,8 +2,8 @@ Parbad.Gateway.PayPing Parbad.Gateway.PayPing - 1.3.0 - netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0 + 1.4.0 + netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0;net9.0 latest Ali Zaferany Copyright © Parbad 2016 @@ -16,7 +16,7 @@ LGPL-3.0-or-later false true - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 PayPing Gateway for Parbad project. For more information see: https://github.com/Sina-Soltani/Parbad diff --git a/src/Parbad.Gateway/Sepehr/src/Parbad.Gateway.Sepehr.csproj b/src/Parbad.Gateway/Sepehr/src/Parbad.Gateway.Sepehr.csproj index 5c2e5921..e4f121d9 100644 --- a/src/Parbad.Gateway/Sepehr/src/Parbad.Gateway.Sepehr.csproj +++ b/src/Parbad.Gateway/Sepehr/src/Parbad.Gateway.Sepehr.csproj @@ -3,8 +3,8 @@ Parbad.Gateway.Sepehr Parbad.Gateway.Sepehr - 1.5.0 - netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0 + 1.6.0 + netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0;net9.0 latest Sina Soltani Copyright © Sina Soltani 2016 @@ -16,7 +16,7 @@ true parbad Payment gateway bank Iran shetab sepehr mabna پرداخت درگاه بانک ایران شتاب سپهر مبنا کارت true - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 Sepehr Gateway for Parbad project. For more information see: https://www.nuget.org/packages/Parbad/ diff --git a/src/Parbad.Gateway/YekPay/src/Parbad.Gateway.YekPay.csproj b/src/Parbad.Gateway/YekPay/src/Parbad.Gateway.YekPay.csproj index 9a41c381..6bb11d81 100644 --- a/src/Parbad.Gateway/YekPay/src/Parbad.Gateway.YekPay.csproj +++ b/src/Parbad.Gateway/YekPay/src/Parbad.Gateway.YekPay.csproj @@ -3,8 +3,8 @@ Parbad.Gateway.YekPay Parbad.Gateway.YekPay - 1.4.0 - netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0 + 1.5.0 + netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0;net9.0 latest Sina Soltani Copyright © Sina Soltani 2016 @@ -16,7 +16,7 @@ true parbad Payment gateway bank Iran shetab yekpay پرداخت درگاه بانک ایران شتاب یک پی true - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 YekPay Gateway for Parbad project. For more information see: https://www.nuget.org/packages/Parbad/ diff --git a/src/Parbad.Gateway/ZarinPal/src/Parbad.Gateway.ZarinPal.csproj b/src/Parbad.Gateway/ZarinPal/src/Parbad.Gateway.ZarinPal.csproj index 10282047..c8591369 100644 --- a/src/Parbad.Gateway/ZarinPal/src/Parbad.Gateway.ZarinPal.csproj +++ b/src/Parbad.Gateway/ZarinPal/src/Parbad.Gateway.ZarinPal.csproj @@ -3,8 +3,8 @@ Parbad.Gateway.ZarinPal Parbad.Gateway.ZarinPal - 1.5.0 - netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net6.0;net7.0;net8 + 1.6.0 + netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net6.0;net7.0;net8;net9.0 latest Sina Soltani Copyright © Sina Soltani 2016 @@ -19,7 +19,7 @@ ZarinPal Gateway for Parbad project. For more information see: https://github.com/Sina-Soltani/Parbad - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 diff --git a/src/Parbad.Gateway/Zibal/src/Parbad.Gateway.Zibal.csproj b/src/Parbad.Gateway/Zibal/src/Parbad.Gateway.Zibal.csproj index 05cda53b..4f36f226 100644 --- a/src/Parbad.Gateway/Zibal/src/Parbad.Gateway.Zibal.csproj +++ b/src/Parbad.Gateway/Zibal/src/Parbad.Gateway.Zibal.csproj @@ -4,8 +4,8 @@ Parbad.Gateway.Zibal Parbad.Gateway.Zibal enable - 1.2.0 - netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0 + 1.3.0 + netstandard2.0;netstandard2.1;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0;net9.0 latest Mohammad Ashrafi Copyright © Sina Soltani 2016 @@ -17,7 +17,7 @@ true parbad Payment Gateway Bank Iran Shetab zibal پرداخت درگاه بانک ایران شتاب زیبال True - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 Zibal Gateway for Parbad project. For more information see: https://github.com/Sina-Soltani/Parbad diff --git a/src/Parbad.Shared/DependencyInjectionExtensions.cs b/src/Parbad.Shared/DependencyInjectionExtensions.cs index 15d78fbf..9aad7c6f 100644 --- a/src/Parbad.Shared/DependencyInjectionExtensions.cs +++ b/src/Parbad.Shared/DependencyInjectionExtensions.cs @@ -1,103 +1,102 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using System; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +internal static class DependencyInjectionExtensions { - internal static class DependencyInjectionExtensions + public static IServiceCollection Add(this IServiceCollection services, Type serviceType, ServiceLifetime lifetime) + { + services.Add(ServiceDescriptor.Describe(serviceType, serviceType, lifetime)); + + return services; + } + + public static IServiceCollection Add(this IServiceCollection services, ServiceLifetime lifetime) where TService : class + { + services.Add(ServiceDescriptor.Describe(typeof(TService), typeof(TService), lifetime)); + + return services; + } + + public static IServiceCollection Add(this IServiceCollection services, ServiceLifetime lifetime) + where TService : class + where TImplementation : class, TService + { + services.Add(ServiceDescriptor.Describe(typeof(TService), typeof(TImplementation), lifetime)); + + return services; + } + + public static IServiceCollection Add(this IServiceCollection services, Func factory, ServiceLifetime lifetime) + where TService : class + { + services.Add(ServiceDescriptor.Describe(typeof(TService), factory, lifetime)); + + return services; + } + + public static IServiceCollection TryAdd(this IServiceCollection services, Type serviceType, ServiceLifetime lifetime) + { + services.TryAdd(ServiceDescriptor.Describe(serviceType, serviceType, lifetime)); + + return services; + } + + public static IServiceCollection TryAdd(this IServiceCollection services, ServiceLifetime lifetime) where TService : class + { + services.TryAdd(ServiceDescriptor.Describe(typeof(TService), typeof(TService), lifetime)); + + return services; + } + + public static IServiceCollection TryAdd(this IServiceCollection services, ServiceLifetime lifetime) + where TService : class + where TImplementation : class, TService + { + services.TryAdd(ServiceDescriptor.Describe(typeof(TService), typeof(TImplementation), lifetime)); + + return services; + } + + public static IServiceCollection TryAdd(this IServiceCollection services, Func factory, ServiceLifetime lifetime) + where TService : class + { + services.TryAdd(ServiceDescriptor.Describe(typeof(TService), factory, lifetime)); + + return services; + } + + public static IServiceCollection AddOrUpdate(this IServiceCollection services, ServiceLifetime lifetime) + where TService : class + { + return services + .RemoveAll() + .Add(lifetime); + } + + public static IServiceCollection AddOrUpdate(this IServiceCollection services, ServiceLifetime lifetime) + where TService : class + where TImplementation : class, TService + { + return services + .RemoveAll() + .Add(lifetime); + } + + public static IServiceCollection AddOrUpdate(this IServiceCollection services, Func factory, ServiceLifetime lifetime) + where TService : class + { + return services + .RemoveAll() + .Add(factory, lifetime); + } + + public static IServiceCollection AddOrUpdate(this IServiceCollection services, TService implementationInstance) + where TService : class { - public static IServiceCollection Add(this IServiceCollection services, Type serviceType, ServiceLifetime lifetime) - { - services.Add(ServiceDescriptor.Describe(serviceType, serviceType, lifetime)); - - return services; - } - - public static IServiceCollection Add(this IServiceCollection services, ServiceLifetime lifetime) where TService : class - { - services.Add(ServiceDescriptor.Describe(typeof(TService), typeof(TService), lifetime)); - - return services; - } - - public static IServiceCollection Add(this IServiceCollection services, ServiceLifetime lifetime) - where TService : class - where TImplementation : class, TService - { - services.Add(ServiceDescriptor.Describe(typeof(TService), typeof(TImplementation), lifetime)); - - return services; - } - - public static IServiceCollection Add(this IServiceCollection services, Func factory, ServiceLifetime lifetime) - where TService : class - { - services.Add(ServiceDescriptor.Describe(typeof(TService), factory, lifetime)); - - return services; - } - - public static IServiceCollection TryAdd(this IServiceCollection services, Type serviceType, ServiceLifetime lifetime) - { - services.TryAdd(ServiceDescriptor.Describe(serviceType, serviceType, lifetime)); - - return services; - } - - public static IServiceCollection TryAdd(this IServiceCollection services, ServiceLifetime lifetime) where TService : class - { - services.TryAdd(ServiceDescriptor.Describe(typeof(TService), typeof(TService), lifetime)); - - return services; - } - - public static IServiceCollection TryAdd(this IServiceCollection services, ServiceLifetime lifetime) - where TService : class - where TImplementation : class, TService - { - services.TryAdd(ServiceDescriptor.Describe(typeof(TService), typeof(TImplementation), lifetime)); - - return services; - } - - public static IServiceCollection TryAdd(this IServiceCollection services, Func factory, ServiceLifetime lifetime) - where TService : class - { - services.TryAdd(ServiceDescriptor.Describe(typeof(TService), factory, lifetime)); - - return services; - } - - public static IServiceCollection AddOrUpdate(this IServiceCollection services, ServiceLifetime lifetime) - where TService : class - { - return services - .RemoveAll() - .Add(lifetime); - } - - public static IServiceCollection AddOrUpdate(this IServiceCollection services, ServiceLifetime lifetime) - where TService : class - where TImplementation : class, TService - { - return services - .RemoveAll() - .Add(lifetime); - } - - public static IServiceCollection AddOrUpdate(this IServiceCollection services, Func factory, ServiceLifetime lifetime) - where TService : class - { - return services - .RemoveAll() - .Add(factory, lifetime); - } - - public static IServiceCollection AddOrUpdate(this IServiceCollection services, TService implementationInstance) - where TService : class - { - return services - .RemoveAll() - .AddSingleton(implementationInstance); - } + return services + .RemoveAll() + .AddSingleton(implementationInstance); } } diff --git a/src/Parbad.Storage/Abstractions/IStorage.cs b/src/Parbad.Storage/Abstractions/IStorage.cs index d1d9434a..ca8f8545 100644 --- a/src/Parbad.Storage/Abstractions/IStorage.cs +++ b/src/Parbad.Storage/Abstractions/IStorage.cs @@ -1,8 +1,10 @@ // Copyright (c) Parbad. All rights reserved. // Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. -using Parbad.Storage.Abstractions.Models; +using System; +using System.Collections.Generic; using System.Linq; +using Parbad.Storage.Abstractions.Models; using System.Threading; using System.Threading.Tasks; @@ -16,11 +18,13 @@ public interface IStorage /// /// Gets a list of . /// + [Obsolete("This property will be removed in a future release. The usages are implemented as methods now.")] IQueryable Payments { get; } /// /// Gets a list of . /// + [Obsolete("This property will be removed in a future release. The usages are implemented as methods now.")] IQueryable Transactions { get; } /// @@ -64,4 +68,39 @@ public interface IStorage /// /// Task DeleteTransactionAsync(Transaction transaction, CancellationToken cancellationToken = default); + + /// + /// Gets a payment by its tracking number. + /// + /// + /// + Task GetPaymentByTrackingNumberAsync(long trackingNumber, CancellationToken cancellationToken = default); + + /// + /// Gets a payment by its token. + /// + /// + /// + Task GetPaymentByTokenAsync(string paymentToken, CancellationToken cancellationToken = default); + + /// + /// Checks whether a payment exists with the given . + /// + /// + /// + Task DoesPaymentExistAsync(long trackingNumber, CancellationToken cancellationToken = default); + + /// + /// Checks whether a payment exists with the given . + /// + /// + /// + Task DoesPaymentExistAsync(string paymentToken, CancellationToken cancellationToken = default); + + /// + /// Gets a list of transactions of the given . + /// + /// + /// + Task> GetTransactionsAsync(Payment payment, CancellationToken cancellationToken = default); } diff --git a/src/Parbad.Storage/Abstractions/IStorageManager.cs b/src/Parbad.Storage/Abstractions/IStorageManager.cs index e1058f57..2a91753a 100644 --- a/src/Parbad.Storage/Abstractions/IStorageManager.cs +++ b/src/Parbad.Storage/Abstractions/IStorageManager.cs @@ -1,71 +1,12 @@ // Copyright (c) Parbad. All rights reserved. // Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. -using Parbad.Storage.Abstractions.Models; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; +using System; namespace Parbad.Storage.Abstractions; /// -/// Provides the APIs for managing payment and transaction in a persistence store. +/// Provides the APIs for managing payments and transactions in a persistence store. /// -public interface IStorageManager -{ - /// - /// Creates a new payment. - /// - /// - /// - Task CreatePaymentAsync(Payment payment, CancellationToken cancellationToken = default); - - /// - /// Updates the given . - /// - /// - /// - Task UpdatePaymentAsync(Payment payment, CancellationToken cancellationToken = default); - - /// - /// Creates a new transaction. - /// - /// - /// - Task CreateTransactionAsync(Transaction transaction, CancellationToken cancellationToken = default); - - /// - /// Gets a payment by its tracking number. - /// - /// - /// - Task GetPaymentByTrackingNumberAsync(long trackingNumber, CancellationToken cancellationToken = default); - - /// - /// Gets a payment by its token. - /// - /// - /// - Task GetPaymentByTokenAsync(string paymentToken, CancellationToken cancellationToken = default); - - /// - /// Checks whether a payment exists with the given . - /// - /// - /// - Task DoesPaymentExistAsync(long trackingNumber, CancellationToken cancellationToken = default); - - /// - /// Checks whether a payment exists with the given . - /// - /// - /// - Task DoesPaymentExistAsync(string paymentToken, CancellationToken cancellationToken = default); - - /// - /// Gets a list of transactions of the given . - /// - /// - /// - Task> GetTransactionsAsync(Payment payment, CancellationToken cancellationToken = default); -} +[Obsolete("This interface will be removed in a future release. All methods are moved to IStorage interface.")] +public interface IStorageManager : IStorage; diff --git a/src/Parbad.Storage/Abstractions/Parbad.Storage.Abstractions.csproj b/src/Parbad.Storage/Abstractions/Parbad.Storage.Abstractions.csproj index 29fbeaa7..98e02b90 100644 --- a/src/Parbad.Storage/Abstractions/Parbad.Storage.Abstractions.csproj +++ b/src/Parbad.Storage/Abstractions/Parbad.Storage.Abstractions.csproj @@ -3,9 +3,10 @@ Parbad.Storage.Abstractions Parbad.Storage.Abstractions - 1.2.0 + 1.3.0 latest - netstandard2.0;netstandard2.1;netcoreapp3.0;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + netstandard2.0;netstandard2.1;netcoreapp3.0;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0 + enable false true LGPL-3.0-or-later @@ -19,7 +20,7 @@ More information: https://github.com/Sina-Soltani/Parbad parbad storage true Sina Soltani - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 @@ -37,7 +38,7 @@ More information: https://github.com/Sina-Soltani/Parbad - + diff --git a/src/Parbad.Storage/Abstractions/Parbad.Storage.Abstractions.xml b/src/Parbad.Storage/Abstractions/Parbad.Storage.Abstractions.xml index 11a720fd..b2fb827d 100644 --- a/src/Parbad.Storage/Abstractions/Parbad.Storage.Abstractions.xml +++ b/src/Parbad.Storage/Abstractions/Parbad.Storage.Abstractions.xml @@ -61,96 +61,75 @@ - - - A builder for building the Parbad storage. - - - - - Specifies the contract for a collection of service descriptors. - - - - - Adds the given storage to Parbad services. - - - Lifetime of the given storage. - - + - Adds the given storage to Parbad services as singleton. + Gets a payment by its tracking number. - + + - + - Adds the given storage to Parbad services. + Gets a payment by its token. - - Lifetime of the given storage. + + - + - Provides the APIs for managing payment and transaction in a persistence store. + Checks whether a payment exists with the given . + + - + - Creates a new payment. + Checks whether a payment exists with the given . - + - + - Updates the given . + Gets a list of transactions of the given . - + - Creates a new transaction. + A builder for building the Parbad storage. - - - + - Gets a payment by its tracking number. + Specifies the contract for a collection of service descriptors. - - - + - Gets a payment by its token. + Adds the given storage to Parbad services. - - + + Lifetime of the given storage. - + - Checks whether a payment exists with the given . + Adds the given storage to Parbad services as singleton. - - + - + - Checks whether a payment exists with the given . + Adds the given storage to Parbad services. - - + + Lifetime of the given storage. - + - Gets a list of transactions of the given . + Provides the APIs for managing payments and transactions in a persistence store. - - @@ -190,5 +169,20 @@ The lifetime of given StorageManager. + + + + + + + + + + + + + + + diff --git a/src/Parbad.Storage/Abstractions/StorageBuilderExtensions.cs b/src/Parbad.Storage/Abstractions/StorageBuilderExtensions.cs index f751e191..6906d29e 100644 --- a/src/Parbad.Storage/Abstractions/StorageBuilderExtensions.cs +++ b/src/Parbad.Storage/Abstractions/StorageBuilderExtensions.cs @@ -12,6 +12,7 @@ public static class StorageBuilderExtensions /// /// /// The lifetime of given StorageManager. + [Obsolete("StorageManager will be removed in a future release. The implementations are moved to the IStorage interface.")] public static IStorageBuilder AddStorageManager(this IStorageBuilder builder, ServiceLifetime lifetime) where TManager : class, IStorageManager { builder.Services.AddOrUpdate(lifetime); @@ -24,6 +25,7 @@ public static IStorageBuilder AddStorageManager(this IStorageBuilder b /// /// /// + [Obsolete("StorageManager will be removed in a future release. The implementations are moved to the IStorage interface.")] public static IStorageBuilder AddStorageManager(this IStorageBuilder builder, IStorageManager storageManager) { if (builder == null) throw new ArgumentNullException(nameof(builder)); @@ -42,6 +44,7 @@ public static IStorageBuilder AddStorageManager(this IStorageBuilder builder, IS /// /// /// The lifetime of given StorageManager. + [Obsolete("StorageManager will be removed in a future release. The implementations are moved to the IStorage interface.")] public static IStorageBuilder AddStorageManager(this IStorageBuilder builder, Func factory, ServiceLifetime lifetime) { if (builder == null) throw new ArgumentNullException(nameof(builder)); diff --git a/src/Parbad.Storage/Abstractions/StorageExtensions.cs b/src/Parbad.Storage/Abstractions/StorageExtensions.cs new file mode 100644 index 00000000..5331c5a9 --- /dev/null +++ b/src/Parbad.Storage/Abstractions/StorageExtensions.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Parbad.Storage.Abstractions.Models; + +namespace Parbad.Storage.Abstractions; + +public static class StorageExtensions +{ + /// + public static Task GetPaymentByTrackingNumberAsync(this IStorage storage, + long trackingNumber, + CancellationToken cancellationToken = default) + { + if (storage == null) throw new ArgumentNullException(nameof(storage)); + + cancellationToken.ThrowIfCancellationRequested(); + + return Task.FromResult(storage.Payments.SingleOrDefault(model => model.TrackingNumber == trackingNumber)); + } + + /// + public static Task GetPaymentByTokenAsync(this IStorage storage, + string paymentToken, + CancellationToken cancellationToken = default) + { + if (storage == null) throw new ArgumentNullException(nameof(storage)); + + cancellationToken.ThrowIfCancellationRequested(); + + return Task.FromResult(storage.Payments.SingleOrDefault(model => model.Token == paymentToken)); + } + + /// + public static Task DoesPaymentExistAsync(this IStorage storage, + long trackingNumber, + CancellationToken cancellationToken = default) + { + if (storage == null) throw new ArgumentNullException(nameof(storage)); + + cancellationToken.ThrowIfCancellationRequested(); + + return Task.FromResult(storage.Payments.Any(model => model.TrackingNumber == trackingNumber)); + } + + /// + public static Task DoesPaymentExistAsync(this IStorage storage, + string paymentToken, + CancellationToken cancellationToken = default) + { + if (storage == null) throw new ArgumentNullException(nameof(storage)); + + return Task.FromResult(storage.Payments.Any(model => model.Token == paymentToken)); + } + + /// + public static Task> GetTransactionsAsync(this IStorage storage, + Payment payment, + CancellationToken cancellationToken = default) + { + if (storage == null) throw new ArgumentNullException(nameof(storage)); + + return Task.FromResult(storage.Transactions.Where(model => model.PaymentId == payment.Id).ToList()); + } +} diff --git a/src/Parbad.Storage/Cache/src/Abstractions/CacheStorage.cs b/src/Parbad.Storage/Cache/src/Abstractions/CacheStorage.cs index a9ffe426..66d355a5 100644 --- a/src/Parbad.Storage/Cache/src/Abstractions/CacheStorage.cs +++ b/src/Parbad.Storage/Cache/src/Abstractions/CacheStorage.cs @@ -4,6 +4,7 @@ using Parbad.Storage.Abstractions; using Parbad.Storage.Abstractions.Models; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -35,6 +36,7 @@ public virtual Task CreatePaymentAsync(Payment payment, CancellationToken cancel payment.Id = GenerateNewPaymentId(); var record = FindPayment(payment); + if (record != null) throw new InvalidOperationException($"There is already a payment record in database with id {payment.Id}"); Collection.Payments.Add(payment); @@ -85,6 +87,7 @@ public virtual Task CreateTransactionAsync(Transaction transaction, Cancellation transaction.Id = GenerateNewTransactionId(); var record = FindTransaction(transaction); + if (record != null) throw new InvalidOperationException($"There is already a transaction record in database with id {transaction.Id}"); Collection.Transactions.Add(transaction); @@ -122,11 +125,61 @@ public virtual Task DeleteTransactionAsync(Transaction transaction, Cancellation return SaveChangesAsync(cancellationToken); } + /// + public virtual Task GetPaymentByTrackingNumberAsync(long trackingNumber, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var record = Collection.Payments.SingleOrDefault(payment => payment.TrackingNumber == trackingNumber); + + return Task.FromResult(record); + } + + /// + public virtual Task GetPaymentByTokenAsync(string paymentToken, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var record = Collection.Payments.SingleOrDefault(payment => payment.Token == paymentToken); + + return Task.FromResult(record); + } + + /// + public virtual Task DoesPaymentExistAsync(long trackingNumber, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var exists = Collection.Payments.Any(payment => payment.TrackingNumber == trackingNumber); + + return Task.FromResult(exists); + } + + /// + public virtual Task DoesPaymentExistAsync(string paymentToken, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var exists = Collection.Payments.Any(payment => payment.Token == paymentToken); + + return Task.FromResult(exists); + } + + /// + public virtual Task> GetTransactionsAsync(Payment payment, CancellationToken cancellationToken = default) + { + var transactions = Collection.Transactions + .Where(transaction => transaction.PaymentId == payment.Id) + .ToList(); + + return Task.FromResult(transactions); + } + /// /// Finds a payment in storage. /// /// - protected virtual Payment FindPayment(Payment payment) + protected virtual Payment? FindPayment(Payment payment) { return Collection.Payments.Contains(payment) ? payment @@ -137,7 +190,7 @@ protected virtual Payment FindPayment(Payment payment) /// Finds a transaction in storage. /// /// - protected virtual Transaction FindTransaction(Transaction transaction) + protected virtual Transaction? FindTransaction(Transaction transaction) { return Collection.Transactions.Contains(transaction) ? transaction diff --git a/src/Parbad.Storage/Cache/src/Parbad.Storage.Cache.csproj b/src/Parbad.Storage/Cache/src/Parbad.Storage.Cache.csproj index d7d418f2..bc851005 100644 --- a/src/Parbad.Storage/Cache/src/Parbad.Storage.Cache.csproj +++ b/src/Parbad.Storage/Cache/src/Parbad.Storage.Cache.csproj @@ -2,9 +2,10 @@ Parbad.Storage.Cache - 1.4.0 + 1.5.0 latest - netstandard2.0;netstandard2.1;netcoreapp3.0;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + netstandard2.0;netstandard2.1;netcoreapp3.0;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0 + enable false true LGPL-3.0-or-later @@ -19,7 +20,7 @@ More information: https://github.com/Sina-Soltani/Parbad true Sina Soltani Parbad.Storage.Cache - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 @@ -41,7 +42,7 @@ More information: https://github.com/Sina-Soltani/Parbad - + diff --git a/src/Parbad.Storage/Cache/src/Parbad.Storage.Cache.xml b/src/Parbad.Storage/Cache/src/Parbad.Storage.Cache.xml index 8f347608..70af4a4f 100644 --- a/src/Parbad.Storage/Cache/src/Parbad.Storage.Cache.xml +++ b/src/Parbad.Storage/Cache/src/Parbad.Storage.Cache.xml @@ -38,6 +38,21 @@ + + + + + + + + + + + + + + + Finds a payment in storage. diff --git a/src/Parbad.Storage/Cache/tests/Parbad.Storage.Cache.Tests.csproj b/src/Parbad.Storage/Cache/tests/Parbad.Storage.Cache.Tests.csproj index f87da84a..ae661946 100644 --- a/src/Parbad.Storage/Cache/tests/Parbad.Storage.Cache.Tests.csproj +++ b/src/Parbad.Storage/Cache/tests/Parbad.Storage.Cache.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 latest false diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/Builder/EntityFrameworkStorageBuilderExtensions.cs b/src/Parbad.Storage/EntityFrameworkCore/src/Builder/EntityFrameworkStorageBuilderExtensions.cs index 7e284355..517c51b7 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/Builder/EntityFrameworkStorageBuilderExtensions.cs +++ b/src/Parbad.Storage/EntityFrameworkCore/src/Builder/EntityFrameworkStorageBuilderExtensions.cs @@ -7,44 +7,40 @@ using Parbad.Storage.EntityFrameworkCore.Options; using System; -namespace Parbad.Storage.EntityFrameworkCore.Builder +namespace Parbad.Storage.EntityFrameworkCore.Builder; + +public static class EntityFrameworkStorageBuilderExtensions { - public static class EntityFrameworkStorageBuilderExtensions + /// + /// Uses the EntityFramework Core as a storage for saving and loading data. + /// + /// Note: It means Parbad can save and load the data in different database providers + /// such as SQL Server, MySql, Sqlite, PostgreSQL, Oracle, InMemory, etc. + /// For more information see: https://docs.microsoft.com/en-us/ef/core/providers/. + /// + /// Note: This database is only for internal usages such as saving and loading payment information. + /// You don't need to think about merging and using this database with your own database. + /// The important payment information such as Tracking Number, Transaction Code, etc. will you get from the result of + /// all payment requests. + /// + /// + /// Configures the EntityFrameworkCore options for Parbad. + public static IStorageBuilder UseEfCore(this IStorageBuilder builder, Action configureEfCoreOptions) { - /// - /// Uses the EntityFramework Core as a storage for saving and loading data. - /// - /// Note: It means Parbad can save and load the data in different database providers - /// such as SQL Server, MySql, Sqlite, PostgreSQL, Oracle, InMemory, etc. - /// For more information see: https://docs.microsoft.com/en-us/ef/core/providers/. - /// - /// Note: This database is only for internal usages such as saving and loading payment information. - /// You don't need to think about merging and using this database with your own database. - /// The important payment information such as Tracking Number, Transaction Code, etc. will you get from the result of - /// all payment requests. - /// - /// - /// Configures the EntityFrameworkCore options for Parbad. - public static IStorageBuilder UseEfCore(this IStorageBuilder builder, Action configureEfCoreOptions) - { - if (builder == null) throw new ArgumentNullException(nameof(builder)); - if (configureEfCoreOptions == null) throw new ArgumentNullException(nameof(configureEfCoreOptions)); - - var options = new EntityFrameworkCoreOptions(); - configureEfCoreOptions(options); - - builder.Services.Configure(configureEfCoreOptions); - - builder.Services.AddDbContext(dbBuilder => - { - options.ConfigureDbContext?.Invoke(dbBuilder); - }); - - builder.AddStorage(ServiceLifetime.Transient); - - builder.AddStorageManager(ServiceLifetime.Transient); - - return builder; - } + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (configureEfCoreOptions == null) throw new ArgumentNullException(nameof(configureEfCoreOptions)); + + var options = new EntityFrameworkCoreOptions(); + configureEfCoreOptions(options); + + builder.Services.Configure(configureEfCoreOptions); + + builder.Services.AddDbContext(dbBuilder => options.ConfigureDbContext?.Invoke(dbBuilder)); + + builder.AddStorage(ServiceLifetime.Transient); + + builder.AddStorageManager(ServiceLifetime.Transient); + + return builder; } } diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/EntityTypeConfiguration.cs b/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/EntityTypeConfiguration.cs index e84d1c0f..9b5b48f5 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/EntityTypeConfiguration.cs +++ b/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/EntityTypeConfiguration.cs @@ -5,36 +5,35 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Parbad.Storage.EntityFrameworkCore.Options; -namespace Parbad.Storage.EntityFrameworkCore.Configuration +namespace Parbad.Storage.EntityFrameworkCore.Configuration; + +/// +/// Applies the configuration on the specified entity. +/// +/// +public abstract class EntityTypeConfiguration : IEntityTypeConfiguration where TEntity : class { /// - /// Applies the configuration on the specified entity. + /// Initializes an instance of . /// - /// - public abstract class EntityTypeConfiguration : IEntityTypeConfiguration where TEntity : class + protected EntityTypeConfiguration(EntityFrameworkCoreOptions efCoreOptions) { - /// - /// Initializes an instance of . - /// - protected EntityTypeConfiguration(EntityFrameworkCoreOptions efCoreOptions) - { - EntityFrameworkCoreOptions = efCoreOptions; - } - - /// - /// Contains the options for configuring the EntityFrameworkCore for Parbad storage. - /// - public EntityFrameworkCoreOptions EntityFrameworkCoreOptions { get; } + EntityFrameworkCoreOptions = efCoreOptions; + } - /// - public void Configure(EntityTypeBuilder builder) - { - Configure(builder, EntityFrameworkCoreOptions); - } + /// + /// Contains the options for configuring the EntityFrameworkCore for Parbad storage. + /// + public EntityFrameworkCoreOptions EntityFrameworkCoreOptions { get; } - /// - /// Configures the entity. - /// - public abstract void Configure(EntityTypeBuilder builder, EntityFrameworkCoreOptions tableOptions); + /// + public void Configure(EntityTypeBuilder builder) + { + Configure(builder, EntityFrameworkCoreOptions); } -} + + /// + /// Configures the entity. + /// + public abstract void Configure(EntityTypeBuilder builder, EntityFrameworkCoreOptions tableOptions); +} \ No newline at end of file diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/EntityTypeConfigurationExtensions.cs b/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/EntityTypeConfigurationExtensions.cs index 68bfacf9..7207e71b 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/EntityTypeConfigurationExtensions.cs +++ b/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/EntityTypeConfigurationExtensions.cs @@ -6,31 +6,30 @@ using Parbad.Storage.EntityFrameworkCore.Options; using System; -namespace Parbad.Storage.EntityFrameworkCore.Configuration +namespace Parbad.Storage.EntityFrameworkCore.Configuration; + +internal static class EntityTypeConfigurationExtensions { - internal static class EntityTypeConfigurationExtensions + public static EntityTypeBuilder ToTable(this EntityTypeBuilder entityTypeBuilder, TableOptions options, string defaultSchema) { - public static EntityTypeBuilder ToTable(this EntityTypeBuilder entityTypeBuilder, TableOptions options, string defaultSchema) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - - var schema = string.IsNullOrWhiteSpace(options.Schema) ? defaultSchema : options.Schema; + if (options == null) throw new ArgumentNullException(nameof(options)); - if (string.IsNullOrWhiteSpace(options.Name)) - { - throw new Exception("Table name cannot be null or empty."); - } + var schema = string.IsNullOrWhiteSpace(options.Schema) ? defaultSchema : options.Schema; - if (string.IsNullOrWhiteSpace(schema)) - { - entityTypeBuilder.ToTable(options.Name); - } - else - { - entityTypeBuilder.ToTable(options.Name, schema); - } + if (string.IsNullOrWhiteSpace(options.Name)) + { + throw new Exception("Table name cannot be null or empty."); + } - return entityTypeBuilder; + if (string.IsNullOrWhiteSpace(schema)) + { + entityTypeBuilder.ToTable(options.Name); + } + else + { + entityTypeBuilder.ToTable(options.Name, schema); } + + return entityTypeBuilder; } -} +} \ No newline at end of file diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/PaymentConfiguration.cs b/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/PaymentConfiguration.cs index c9031cff..cff177f8 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/PaymentConfiguration.cs +++ b/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/PaymentConfiguration.cs @@ -6,81 +6,80 @@ using Parbad.Storage.EntityFrameworkCore.Domain; using Parbad.Storage.EntityFrameworkCore.Options; -namespace Parbad.Storage.EntityFrameworkCore.Configuration +namespace Parbad.Storage.EntityFrameworkCore.Configuration; + +/// +/// Payment entity configuration. +/// +public class PaymentConfiguration : EntityTypeConfiguration { /// - /// Payment entity configuration. + /// Initializes an instance of . /// - public class PaymentConfiguration : EntityTypeConfiguration + public PaymentConfiguration(EntityFrameworkCoreOptions efCoreOptions) : base(efCoreOptions) + { + } + + /// + public override void Configure(EntityTypeBuilder builder, EntityFrameworkCoreOptions efCoreOptions) { - /// - /// Initializes an instance of . - /// - public PaymentConfiguration(EntityFrameworkCoreOptions efCoreOptions) : base(efCoreOptions) - { - } - - /// - public override void Configure(EntityTypeBuilder builder, EntityFrameworkCoreOptions efCoreOptions) - { - builder.ToTable(efCoreOptions.PaymentTableOptions, efCoreOptions.DefaultSchema); - - builder - .HasKey(entity => entity.Id) - .HasName("payment_id"); - builder.Property(entity => entity.Id) - .HasColumnName("payment_id") - .ValueGeneratedOnAdd(); - - builder.Property(entity => entity.TrackingNumber) - .HasColumnName("tracking_number") - .IsRequired(required: true); - builder.HasIndex(entity => entity.TrackingNumber).IsUnique(unique: true); - - builder.Property(entity => entity.Token) - .HasColumnName(nameof(PaymentEntity.Token).ToLower()) - .IsRequired(required: true); - builder.HasIndex(entity => entity.Token).IsUnique(unique: true); - - builder.Property(entity => entity.Amount) - .HasColumnName(nameof(PaymentEntity.Amount).ToLower()) - .HasColumnType("decimal(18,2)") - .IsRequired(required: true); - - builder.Property(entity => entity.TransactionCode) - .HasColumnName("transaction_code") - .IsRequired(required: false); - - builder.Property(entity => entity.GatewayName) - .HasColumnName("gateway_name") - .HasMaxLength(20) - .IsRequired(required: true); - - builder.Property(entity => entity.IsCompleted) - .HasColumnName("is_completed") - .IsRequired(required: true); - - builder.Property(entity => entity.IsPaid) - .HasColumnName("is_paid") - .IsRequired(required: true); - - builder.Property(entity => entity.GatewayAccountName) - .HasColumnName("gateway_account_name") - .IsRequired(required: false); - - builder.Property(entity => entity.CreatedOn) - .HasColumnName("created_on") - .IsRequired(required: true); - - builder.Property(entity => entity.UpdatedOn) - .HasColumnName("updated_on") - .IsRequired(required: false); - - builder - .HasMany(entity => entity.Transactions) - .WithOne(entity => entity.Payment) - .HasForeignKey(entity => entity.PaymentId) - .OnDelete(DeleteBehavior.Cascade); - } + builder.ToTable(efCoreOptions.PaymentTableOptions, efCoreOptions.DefaultSchema); + + builder + .HasKey(entity => entity.Id) + .HasName("payment_id"); + builder.Property(entity => entity.Id) + .HasColumnName("payment_id") + .ValueGeneratedOnAdd(); + + builder.Property(entity => entity.TrackingNumber) + .HasColumnName("tracking_number") + .IsRequired(required: true); + builder.HasIndex(entity => entity.TrackingNumber).IsUnique(unique: true); + + builder.Property(entity => entity.Token) + .HasColumnName(nameof(PaymentEntity.Token).ToLower()) + .IsRequired(required: true); + builder.HasIndex(entity => entity.Token).IsUnique(unique: true); + + builder.Property(entity => entity.Amount) + .HasColumnName(nameof(PaymentEntity.Amount).ToLower()) + .HasColumnType("decimal(18,2)") + .IsRequired(required: true); + + builder.Property(entity => entity.TransactionCode) + .HasColumnName("transaction_code") + .IsRequired(required: false); + + builder.Property(entity => entity.GatewayName) + .HasColumnName("gateway_name") + .HasMaxLength(20) + .IsRequired(required: true); + + builder.Property(entity => entity.IsCompleted) + .HasColumnName("is_completed") + .IsRequired(required: true); + + builder.Property(entity => entity.IsPaid) + .HasColumnName("is_paid") + .IsRequired(required: true); + + builder.Property(entity => entity.GatewayAccountName) + .HasColumnName("gateway_account_name") + .IsRequired(required: false); + + builder.Property(entity => entity.CreatedOn) + .HasColumnName("created_on") + .IsRequired(required: true); + + builder.Property(entity => entity.UpdatedOn) + .HasColumnName("updated_on") + .IsRequired(required: false); + + builder + .HasMany(entity => entity.Transactions) + .WithOne(entity => entity.Payment) + .HasForeignKey(entity => entity.PaymentId) + .OnDelete(DeleteBehavior.Cascade); } -} +} \ No newline at end of file diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/TransactionConfiguration.cs b/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/TransactionConfiguration.cs index 61c46011..267d97db 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/TransactionConfiguration.cs +++ b/src/Parbad.Storage/EntityFrameworkCore/src/Configuration/TransactionConfiguration.cs @@ -6,60 +6,59 @@ using Parbad.Storage.EntityFrameworkCore.Domain; using Parbad.Storage.EntityFrameworkCore.Options; -namespace Parbad.Storage.EntityFrameworkCore.Configuration +namespace Parbad.Storage.EntityFrameworkCore.Configuration; + +/// +/// Transaction entity configuration. +/// +public class TransactionConfiguration : EntityTypeConfiguration { /// - /// Transaction entity configuration. + /// Initializes an instance of . /// - public class TransactionConfiguration : EntityTypeConfiguration + public TransactionConfiguration(EntityFrameworkCoreOptions efCoreOptions) : base(efCoreOptions) { - /// - /// Initializes an instance of . - /// - public TransactionConfiguration(EntityFrameworkCoreOptions efCoreOptions) : base(efCoreOptions) - { - } + } - /// - public override void Configure(EntityTypeBuilder builder, EntityFrameworkCoreOptions efCoreOptions) - { - builder.ToTable(efCoreOptions.TransactionTableOptions, efCoreOptions.DefaultSchema); + /// + public override void Configure(EntityTypeBuilder builder, EntityFrameworkCoreOptions efCoreOptions) + { + builder.ToTable(efCoreOptions.TransactionTableOptions, efCoreOptions.DefaultSchema); - builder - .HasKey(entity => entity.Id) - .HasName("transaction_id"); - builder.Property(entity => entity.Id) - .HasColumnName("transaction_id") - .ValueGeneratedOnAdd(); + builder + .HasKey(entity => entity.Id) + .HasName("transaction_id"); + builder.Property(entity => entity.Id) + .HasColumnName("transaction_id") + .ValueGeneratedOnAdd(); - builder.Property(entity => entity.Amount) - .HasColumnName(nameof(TransactionEntity.Amount).ToLower()) - .HasColumnType("decimal(18,2)") - .IsRequired(required: true); + builder.Property(entity => entity.Amount) + .HasColumnName(nameof(TransactionEntity.Amount).ToLower()) + .HasColumnType("decimal(18,2)") + .IsRequired(required: true); - builder.Property(entity => entity.Type) - .HasColumnName(nameof(TransactionEntity.Type).ToLower()) - .IsRequired(required: true); + builder.Property(entity => entity.Type) + .HasColumnName(nameof(TransactionEntity.Type).ToLower()) + .IsRequired(required: true); - builder.Property(entity => entity.IsSucceed) - .HasColumnName("is_succeed") - .IsRequired(required: true); + builder.Property(entity => entity.IsSucceed) + .HasColumnName("is_succeed") + .IsRequired(required: true); - builder.Property(entity => entity.Message) - .HasColumnName(nameof(TransactionEntity.Message).ToLower()) - .IsRequired(required: false); + builder.Property(entity => entity.Message) + .HasColumnName(nameof(TransactionEntity.Message).ToLower()) + .IsRequired(required: false); - builder.Property(entity => entity.AdditionalData) - .HasColumnName("additional_data") - .IsRequired(required: false); + builder.Property(entity => entity.AdditionalData) + .HasColumnName("additional_data") + .IsRequired(required: false); - builder.Property(entity => entity.CreatedOn) - .HasColumnName("created_on") - .IsRequired(required: true); + builder.Property(entity => entity.CreatedOn) + .HasColumnName("created_on") + .IsRequired(required: true); - builder.Property(entity => entity.UpdatedOn) - .HasColumnName("updated_on") - .IsRequired(required: false); - } + builder.Property(entity => entity.UpdatedOn) + .HasColumnName("updated_on") + .IsRequired(required: false); } -} +} \ No newline at end of file diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/Context/ParbadDataContext.cs b/src/Parbad.Storage/EntityFrameworkCore/src/Context/ParbadDataContext.cs index ba18e094..856cb275 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/Context/ParbadDataContext.cs +++ b/src/Parbad.Storage/EntityFrameworkCore/src/Context/ParbadDataContext.cs @@ -7,26 +7,25 @@ using Parbad.Storage.EntityFrameworkCore.Domain; using Parbad.Storage.EntityFrameworkCore.Options; -namespace Parbad.Storage.EntityFrameworkCore.Context +namespace Parbad.Storage.EntityFrameworkCore.Context; + +public class ParbadDataContext : DbContext { - public class ParbadDataContext : DbContext + public ParbadDataContext(DbContextOptions options, IOptions efCoreOptions) + : base(options) { - public ParbadDataContext(DbContextOptions options, IOptions efCoreOptions) : base(options) - { - EntityFrameworkCoreOptions = efCoreOptions.Value; - } + EntityFrameworkCoreOptions = efCoreOptions.Value; + } - public EntityFrameworkCoreOptions EntityFrameworkCoreOptions { get; } + public EntityFrameworkCoreOptions EntityFrameworkCoreOptions { get; } - public DbSet Payments { get; set; } + public DbSet Payments { get; set; } - public DbSet Transactions { get; set; } + public DbSet Transactions { get; set; } - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder - .ApplyConfiguration(new PaymentConfiguration(EntityFrameworkCoreOptions)) - .ApplyConfiguration(new TransactionConfiguration(EntityFrameworkCoreOptions)); - } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new PaymentConfiguration(EntityFrameworkCoreOptions)) + .ApplyConfiguration(new TransactionConfiguration(EntityFrameworkCoreOptions)); } } diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/Domain/PaymentEntity.cs b/src/Parbad.Storage/EntityFrameworkCore/src/Domain/PaymentEntity.cs index 50edc535..23311e0e 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/Domain/PaymentEntity.cs +++ b/src/Parbad.Storage/EntityFrameworkCore/src/Domain/PaymentEntity.cs @@ -4,32 +4,31 @@ using System; using System.Collections.Generic; -namespace Parbad.Storage.EntityFrameworkCore.Domain +namespace Parbad.Storage.EntityFrameworkCore.Domain; + +public class PaymentEntity { - public class PaymentEntity - { - public long Id { get; set; } + public long Id { get; set; } - public long TrackingNumber { get; set; } + public long TrackingNumber { get; set; } - public decimal Amount { get; set; } + public decimal Amount { get; set; } - public string Token { get; set; } + public string Token { get; set; } - public string TransactionCode { get; set; } + public string TransactionCode { get; set; } - public string GatewayName { get; set; } + public string GatewayName { get; set; } - public string GatewayAccountName { get; set; } + public string GatewayAccountName { get; set; } - public bool IsCompleted { get; set; } + public bool IsCompleted { get; set; } - public bool IsPaid { get; set; } + public bool IsPaid { get; set; } - public DateTime CreatedOn { get; set; } + public DateTime CreatedOn { get; set; } - public DateTime? UpdatedOn { get; set; } + public DateTime? UpdatedOn { get; set; } - public List Transactions { get; set; } = new List(); - } -} + public List Transactions { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/Domain/TransactionEntity.cs b/src/Parbad.Storage/EntityFrameworkCore/src/Domain/TransactionEntity.cs index 5652e80d..fa19b4a6 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/Domain/TransactionEntity.cs +++ b/src/Parbad.Storage/EntityFrameworkCore/src/Domain/TransactionEntity.cs @@ -4,28 +4,27 @@ using Parbad.Storage.Abstractions.Models; using System; -namespace Parbad.Storage.EntityFrameworkCore.Domain +namespace Parbad.Storage.EntityFrameworkCore.Domain; + +public class TransactionEntity { - public class TransactionEntity - { - public long Id { get; set; } + public long Id { get; set; } - public decimal Amount { get; set; } + public decimal Amount { get; set; } - public TransactionType Type { get; set; } + public TransactionType Type { get; set; } - public bool IsSucceed { get; set; } + public bool IsSucceed { get; set; } - public string Message { get; set; } + public string Message { get; set; } - public string AdditionalData { get; set; } + public string AdditionalData { get; set; } - public long PaymentId { get; set; } + public long PaymentId { get; set; } - public DateTime CreatedOn { get; set; } + public DateTime CreatedOn { get; set; } - public DateTime? UpdatedOn { get; set; } + public DateTime? UpdatedOn { get; set; } - public PaymentEntity Payment { get; set; } - } -} + public PaymentEntity Payment { get; set; } +} \ No newline at end of file diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/EntityFrameworkCoreStorage.cs b/src/Parbad.Storage/EntityFrameworkCore/src/EntityFrameworkCoreStorage.cs index c6f4e4b9..304c07b8 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/EntityFrameworkCoreStorage.cs +++ b/src/Parbad.Storage/EntityFrameworkCore/src/EntityFrameworkCoreStorage.cs @@ -7,139 +7,183 @@ using Parbad.Storage.EntityFrameworkCore.Context; using Parbad.Storage.EntityFrameworkCore.Internal; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace Parbad.Storage.EntityFrameworkCore +namespace Parbad.Storage.EntityFrameworkCore; + +/// +/// EntityFramework Core implementation of . +/// +public class EntityFrameworkCoreStorage : IStorage { /// - /// EntityFramework Core implementation of . + /// Initializes an instance of . + /// + /// + public EntityFrameworkCoreStorage(ParbadDataContext context) + { + Context = context; + } + + /// + [Obsolete("This property will be removed in a future release. The usages are implemented as methods now.")] + public virtual IQueryable Payments => Context.Payments.Select(Mapper.ToPaymentModel()); + + /// + [Obsolete("This property will be removed in a future release. The usages are implemented as methods now.")] + public virtual IQueryable Transactions => Context.Transactions.Select(Mapper.ToTransactionModel()); + + /// + /// Gets Parbad DbContext. /// - public class EntityFrameworkCoreStorage : IStorage + protected ParbadDataContext Context { get; } + + /// + public virtual async Task CreatePaymentAsync(Payment payment, CancellationToken cancellationToken = default) { - /// - /// Initializes an instance of . - /// - /// - public EntityFrameworkCoreStorage(ParbadDataContext context) - { - Context = context; - } + if (payment == null) throw new ArgumentNullException(nameof(payment)); + + var entity = payment.ToPaymentEntity(); + entity.CreatedOn = DateTime.UtcNow; + + Context.Payments.Add(entity); + + await Context.SaveChangesAsync(cancellationToken); - /// - public virtual IQueryable Payments => Context.Payments.Select(Mapper.ToPaymentModel()); + Context.Entry(entity).State = EntityState.Detached; - /// - public virtual IQueryable Transactions => Context.Transactions.Select(Mapper.ToTransactionModel()); + payment.Id = entity.Id; + } - /// - /// Parbad DbContext. - /// - protected ParbadDataContext Context { get; } + /// + public virtual async Task UpdatePaymentAsync(Payment payment, CancellationToken cancellationToken = default) + { + if (payment == null) throw new ArgumentNullException(nameof(payment)); - /// - public virtual async Task CreatePaymentAsync(Payment payment, CancellationToken cancellationToken = default) - { - if (payment == null) throw new ArgumentNullException(nameof(payment)); + var record = await Context + .Payments + .AsNoTracking() + .SingleOrDefaultAsync(model => model.Id == payment.Id, cancellationToken); - var entity = payment.ToPaymentEntity(); - entity.CreatedOn = DateTime.UtcNow; + if (record == null) throw new InvalidOperationException($"No payment records found in database with id {payment.Id}"); - Context.Payments.Add(entity); + Mapper.ToPaymentEntity(payment, record); + record.UpdatedOn = DateTime.UtcNow; - await Context.SaveChangesAsync(cancellationToken); + Context.Payments.Update(record); - Context.Entry(entity).State = EntityState.Detached; + await Context.SaveChangesAsync(cancellationToken); + } - payment.Id = entity.Id; - } + /// + public virtual async Task DeletePaymentAsync(Payment payment, CancellationToken cancellationToken = default) + { + if (payment == null) throw new ArgumentNullException(nameof(payment)); - /// - public virtual async Task UpdatePaymentAsync(Payment payment, CancellationToken cancellationToken = default) - { - if (payment == null) throw new ArgumentNullException(nameof(payment)); + var record = await Context + .Payments + .AsNoTracking() + .SingleOrDefaultAsync(model => model.Id == payment.Id, cancellationToken); - var record = await Context - .Payments - .AsNoTracking() - .SingleOrDefaultAsync(model => model.Id == payment.Id, cancellationToken); + if (record == null) throw new InvalidOperationException($"No payment records found in database with id {payment.Id}"); - if (record == null) throw new InvalidOperationException($"No payment records found in database with id {payment.Id}"); + Context.Payments.Remove(record); - Mapper.ToPaymentEntity(payment, record); - record.UpdatedOn = DateTime.UtcNow; + await Context.SaveChangesAsync(cancellationToken); + } - Context.Payments.Update(record); + /// + public virtual async Task CreateTransactionAsync(Transaction transaction, CancellationToken cancellationToken = default) + { + if (transaction == null) throw new ArgumentNullException(nameof(transaction)); - await Context.SaveChangesAsync(cancellationToken); - } + var entity = transaction.ToTransactionEntity(); + entity.CreatedOn = DateTime.UtcNow; - /// - public virtual async Task DeletePaymentAsync(Payment payment, CancellationToken cancellationToken = default) - { - if (payment == null) throw new ArgumentNullException(nameof(payment)); + Context.Transactions.Add(entity); - var record = await Context - .Payments - .AsNoTracking() - .SingleOrDefaultAsync(model => model.Id == payment.Id, cancellationToken); + await Context.SaveChangesAsync(cancellationToken); - if (record == null) throw new InvalidOperationException($"No payment records found in database with id {payment.Id}"); + transaction.Id = entity.Id; + } - Context.Payments.Remove(record); + /// + public virtual async Task UpdateTransactionAsync(Transaction transaction, CancellationToken cancellationToken = default) + { + if (transaction == null) throw new ArgumentNullException(nameof(transaction)); - await Context.SaveChangesAsync(cancellationToken); - } + var record = await Context + .Transactions + .SingleOrDefaultAsync(model => model.Id == transaction.Id, cancellationToken); - /// - public virtual async Task CreateTransactionAsync(Transaction transaction, CancellationToken cancellationToken = default) - { - if (transaction == null) throw new ArgumentNullException(nameof(transaction)); + if (record == null) throw new InvalidOperationException($"No transaction records found in database with id {transaction.Id}"); - var entity = transaction.ToTransactionEntity(); - entity.CreatedOn = DateTime.UtcNow; + Mapper.ToTransactionEntity(transaction, record); + record.UpdatedOn = DateTime.UtcNow; - Context.Transactions.Add(entity); + Context.Transactions.Update(record); - await Context.SaveChangesAsync(cancellationToken); + await Context.SaveChangesAsync(cancellationToken); + } - transaction.Id = entity.Id; - } + /// + public virtual async Task DeleteTransactionAsync(Transaction transaction, CancellationToken cancellationToken = default) + { + if (transaction == null) throw new ArgumentNullException(nameof(transaction)); - /// - public virtual async Task UpdateTransactionAsync(Transaction transaction, CancellationToken cancellationToken = default) - { - if (transaction == null) throw new ArgumentNullException(nameof(transaction)); + var record = await Context + .Transactions + .SingleOrDefaultAsync(model => model.Id == transaction.Id, cancellationToken); - var record = await Context - .Transactions - .SingleOrDefaultAsync(model => model.Id == transaction.Id, cancellationToken); + if (record == null) throw new InvalidOperationException($"No transaction records found in database with id {transaction.Id}"); - if (record == null) throw new InvalidOperationException($"No transaction records found in database with id {transaction.Id}"); + Context.Transactions.Remove(record); - Mapper.ToTransactionEntity(transaction, record); - record.UpdatedOn = DateTime.UtcNow; + await Context.SaveChangesAsync(cancellationToken); + } - Context.Transactions.Update(record); + /// + public virtual async Task GetPaymentByTrackingNumberAsync(long trackingNumber, CancellationToken cancellationToken = default) + { + var paymentEntity = await Context.Payments + .AsNoTracking() + .SingleOrDefaultAsync(payment => payment.TrackingNumber == trackingNumber, cancellationToken); - await Context.SaveChangesAsync(cancellationToken); - } + return paymentEntity?.ToPaymentModel(); + } - /// - public virtual async Task DeleteTransactionAsync(Transaction transaction, CancellationToken cancellationToken = default) - { - if (transaction == null) throw new ArgumentNullException(nameof(transaction)); + /// + public virtual async Task GetPaymentByTokenAsync(string paymentToken, CancellationToken cancellationToken = default) + { + var paymentEntity = await Context.Payments + .AsNoTracking() + .SingleOrDefaultAsync(payment => payment.Token == paymentToken, cancellationToken); - var record = await Context - .Transactions - .SingleOrDefaultAsync(model => model.Id == transaction.Id, cancellationToken); + return paymentEntity?.ToPaymentModel(); + } - if (record == null) throw new InvalidOperationException($"No transaction records found in database with id {transaction.Id}"); + /// + public virtual Task DoesPaymentExistAsync(long trackingNumber, CancellationToken cancellationToken = default) + { + return Context.Payments.AnyAsync(payment => payment.TrackingNumber == trackingNumber, cancellationToken); + } - Context.Transactions.Remove(record); + /// + public virtual Task DoesPaymentExistAsync(string paymentToken, CancellationToken cancellationToken = default) + { + return Context.Payments.AnyAsync(payment => payment.Token == paymentToken, cancellationToken); + } - await Context.SaveChangesAsync(cancellationToken); - } + /// + public virtual Task> GetTransactionsAsync(Payment payment, CancellationToken cancellationToken = default) + { + return Context.Transactions + .Where(transaction => transaction.PaymentId == payment.Id) + .AsNoTracking() + .Select(Mapper.ToTransactionModel()) + .ToListAsync(cancellationToken); } } diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/EntityFrameworkCoreStorageManager.cs b/src/Parbad.Storage/EntityFrameworkCore/src/EntityFrameworkCoreStorageManager.cs index 90288a8c..01517ee4 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/EntityFrameworkCoreStorageManager.cs +++ b/src/Parbad.Storage/EntityFrameworkCore/src/EntityFrameworkCoreStorageManager.cs @@ -1,82 +1,11 @@ -using Microsoft.EntityFrameworkCore; +using System; using Parbad.Storage.Abstractions; -using Parbad.Storage.Abstractions.Models; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using Parbad.Storage.EntityFrameworkCore.Context; -namespace Parbad.Storage.EntityFrameworkCore -{ - /// - /// EntityFramework Core implementation of . - /// - public class EntityFrameworkCoreStorageManager : IStorageManager - { - /// - /// Initializes an instance of . - /// - /// - public EntityFrameworkCoreStorageManager(IStorage storage) - { - Storage = storage; - } +namespace Parbad.Storage.EntityFrameworkCore; - protected readonly IStorage Storage; - - /// - public virtual Task CreatePaymentAsync(Payment payment, CancellationToken cancellationToken = default) - { - return Storage.CreatePaymentAsync(payment, cancellationToken); - } - - /// - public virtual Task UpdatePaymentAsync(Payment payment, CancellationToken cancellationToken = default) - { - return Storage.UpdatePaymentAsync(payment, cancellationToken); - } - - /// - public virtual Task CreateTransactionAsync(Transaction transaction, CancellationToken cancellationToken = default) - { - return Storage.CreateTransactionAsync(transaction, cancellationToken); - } - - /// - public virtual Task GetPaymentByTrackingNumberAsync(long trackingNumber, CancellationToken cancellationToken = default) - { - return Storage.Payments - .AsNoTracking() - .SingleOrDefaultAsync(payment => payment.TrackingNumber == trackingNumber, cancellationToken); - } - - /// - public virtual Task GetPaymentByTokenAsync(string paymentToken, CancellationToken cancellationToken = default) - { - return Storage.Payments - .AsNoTracking() - .SingleOrDefaultAsync(payment => payment.Token == paymentToken, cancellationToken); - } - - /// - public virtual Task DoesPaymentExistAsync(long trackingNumber, CancellationToken cancellationToken = default) - { - return Storage.Payments.AnyAsync(payment => payment.TrackingNumber == trackingNumber, cancellationToken); - } - - /// - public virtual Task DoesPaymentExistAsync(string paymentToken, CancellationToken cancellationToken = default) - { - return Storage.Payments.AnyAsync(payment => payment.Token == paymentToken, cancellationToken); - } - - /// - public virtual Task> GetTransactionsAsync(Payment payment, CancellationToken cancellationToken = default) - { - return Storage.Transactions - .Where(transaction => transaction.PaymentId == payment.Id) - .AsNoTracking() - .ToListAsync(cancellationToken); - } - } -} +/// +/// EntityFramework Core implementation of . +/// +[Obsolete("This class will be removed in a future release. The implementations are moved to the EntityFrameworkCoreStorage class.")] +public class EntityFrameworkCoreStorageManager(ParbadDataContext context) : EntityFrameworkCoreStorage(context), IStorageManager; diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/Internal/Mapper.cs b/src/Parbad.Storage/EntityFrameworkCore/src/Internal/Mapper.cs index dc89b858..cf4e9910 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/Internal/Mapper.cs +++ b/src/Parbad.Storage/EntityFrameworkCore/src/Internal/Mapper.cs @@ -6,87 +6,104 @@ using System; using System.Linq.Expressions; -namespace Parbad.Storage.EntityFrameworkCore.Internal +namespace Parbad.Storage.EntityFrameworkCore.Internal; + +internal static class Mapper { - internal static class Mapper + public static PaymentEntity ToPaymentEntity(this Payment model) { - public static PaymentEntity ToPaymentEntity(this Payment model) - { - return new PaymentEntity - { - TrackingNumber = model.TrackingNumber, - Amount = model.Amount, - Token = model.Token, - TransactionCode = model.TransactionCode, - GatewayName = model.GatewayName, - GatewayAccountName = model.GatewayAccountName, - IsCompleted = model.IsCompleted, - IsPaid = model.IsPaid - }; - } + return new PaymentEntity + { + TrackingNumber = model.TrackingNumber, + Amount = model.Amount, + Token = model.Token, + TransactionCode = model.TransactionCode, + GatewayName = model.GatewayName, + GatewayAccountName = model.GatewayAccountName, + IsCompleted = model.IsCompleted, + IsPaid = model.IsPaid + }; + } - public static void ToPaymentEntity(Payment model, PaymentEntity entity) - { - entity.TrackingNumber = model.TrackingNumber; - entity.Amount = model.Amount; - entity.Token = model.Token; - entity.TransactionCode = model.TransactionCode; - entity.GatewayName = model.GatewayName; - entity.GatewayAccountName = model.GatewayAccountName; - entity.IsCompleted = model.IsCompleted; - entity.IsPaid = model.IsPaid; - } + public static void ToPaymentEntity(Payment model, PaymentEntity entity) + { + entity.TrackingNumber = model.TrackingNumber; + entity.Amount = model.Amount; + entity.Token = model.Token; + entity.TransactionCode = model.TransactionCode; + entity.GatewayName = model.GatewayName; + entity.GatewayAccountName = model.GatewayAccountName; + entity.IsCompleted = model.IsCompleted; + entity.IsPaid = model.IsPaid; + } - public static Expression> ToPaymentModel() - { - return entity => new Payment - { - Id = entity.Id, - TrackingNumber = entity.TrackingNumber, - Amount = entity.Amount, - Token = entity.Token, - TransactionCode = entity.TransactionCode, - GatewayName = entity.GatewayName, - GatewayAccountName = entity.GatewayAccountName, - IsCompleted = entity.IsCompleted, - IsPaid = entity.IsPaid - }; - } + public static Expression> ToPaymentModel() + { + return entity => new Payment + { + Id = entity.Id, + TrackingNumber = entity.TrackingNumber, + Amount = entity.Amount, + Token = entity.Token, + TransactionCode = entity.TransactionCode, + GatewayName = entity.GatewayName, + GatewayAccountName = entity.GatewayAccountName, + IsCompleted = entity.IsCompleted, + IsPaid = entity.IsPaid + }; + } - public static TransactionEntity ToTransactionEntity(this Transaction model) - { - return new TransactionEntity - { - Amount = model.Amount, - Type = model.Type, - IsSucceed = model.IsSucceed, - Message = model.Message, - AdditionalData = model.AdditionalData, - PaymentId = model.PaymentId - }; - } + public static Payment ToPaymentModel(this PaymentEntity paymentEntity) + { + if (paymentEntity == null) throw new ArgumentNullException(nameof(paymentEntity)); - public static void ToTransactionEntity(Transaction model, TransactionEntity entity) - { - entity.Amount = model.Amount; - entity.Type = model.Type; - entity.IsSucceed = model.IsSucceed; - entity.Message = model.Message; - entity.AdditionalData = model.AdditionalData; - } + return new() + { + Id = paymentEntity.Id, + TrackingNumber = paymentEntity.TrackingNumber, + Amount = paymentEntity.Amount, + Token = paymentEntity.Token, + TransactionCode = paymentEntity.TransactionCode, + GatewayName = paymentEntity.GatewayName, + GatewayAccountName = paymentEntity.GatewayAccountName, + IsCompleted = paymentEntity.IsCompleted, + IsPaid = paymentEntity.IsPaid + }; + } - public static Expression> ToTransactionModel() - { - return entity => new Transaction - { - Id = entity.Id, - Amount = entity.Amount, - Type = entity.Type, - IsSucceed = entity.IsSucceed, - Message = entity.Message, - AdditionalData = entity.AdditionalData, - PaymentId = entity.PaymentId - }; - } + public static TransactionEntity ToTransactionEntity(this Transaction model) + { + return new TransactionEntity + { + Amount = model.Amount, + Type = model.Type, + IsSucceed = model.IsSucceed, + Message = model.Message, + AdditionalData = model.AdditionalData, + PaymentId = model.PaymentId + }; + } + + public static void ToTransactionEntity(Transaction model, TransactionEntity entity) + { + entity.Amount = model.Amount; + entity.Type = model.Type; + entity.IsSucceed = model.IsSucceed; + entity.Message = model.Message; + entity.AdditionalData = model.AdditionalData; + } + + public static Expression> ToTransactionModel() + { + return entity => new Transaction + { + Id = entity.Id, + Amount = entity.Amount, + Type = entity.Type, + IsSucceed = entity.IsSucceed, + Message = entity.Message, + AdditionalData = entity.AdditionalData, + PaymentId = entity.PaymentId + }; } } diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/Options/EntityFrameworkCoreOptions.cs b/src/Parbad.Storage/EntityFrameworkCore/src/Options/EntityFrameworkCoreOptions.cs index eca0f5f0..a59cd4d7 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/Options/EntityFrameworkCoreOptions.cs +++ b/src/Parbad.Storage/EntityFrameworkCore/src/Options/EntityFrameworkCoreOptions.cs @@ -5,43 +5,42 @@ using Microsoft.EntityFrameworkCore; using Parbad.Storage.EntityFrameworkCore.Context; -namespace Parbad.Storage.EntityFrameworkCore.Options +namespace Parbad.Storage.EntityFrameworkCore.Options; + +/// +/// Contains the options for configuring the EntityFrameworkCore for Parbad storage. +/// +public class EntityFrameworkCoreOptions { /// - /// Contains the options for configuring the EntityFrameworkCore for Parbad storage. + /// Initializes an instance of . /// - public class EntityFrameworkCoreOptions + public EntityFrameworkCoreOptions() { - /// - /// Initializes an instance of . - /// - public EntityFrameworkCoreOptions() - { - DefaultSchema = "parbad"; - - PaymentTableOptions = new TableOptions { Name = "payment" }; - - TransactionTableOptions = new TableOptions { Name = "transaction" }; - } - - /// - /// Configures the . - /// - public Action ConfigureDbContext { get; set; } - - /// - /// Gets or sets the default schema for all tables. The default value is "parbad". - /// - public string DefaultSchema { get; set; } - - /// - /// Contains the options for configuring the Payment table. - /// - public TableOptions PaymentTableOptions { get; set; } - - /// - /// Contains the options for configuring the Transaction table. - /// - public TableOptions TransactionTableOptions { get; set; } + DefaultSchema = "parbad"; + + PaymentTableOptions = new TableOptions { Name = "payment" }; + + TransactionTableOptions = new TableOptions { Name = "transaction" }; } + + /// + /// Configures the . + /// + public Action ConfigureDbContext { get; set; } + + /// + /// Gets or sets the default schema for all tables. The default value is "parbad". + /// + public string DefaultSchema { get; set; } + + /// + /// Contains the options for configuring the Payment table. + /// + public TableOptions PaymentTableOptions { get; set; } + + /// + /// Contains the options for configuring the Transaction table. + /// + public TableOptions TransactionTableOptions { get; set; } } diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/Options/TableOptions.cs b/src/Parbad.Storage/EntityFrameworkCore/src/Options/TableOptions.cs index 38a750b3..da54885f 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/Options/TableOptions.cs +++ b/src/Parbad.Storage/EntityFrameworkCore/src/Options/TableOptions.cs @@ -1,21 +1,20 @@ // Copyright (c) Parbad. All rights reserved. // Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. -namespace Parbad.Storage.EntityFrameworkCore.Options +namespace Parbad.Storage.EntityFrameworkCore.Options; + +/// +/// Contains the options for configuring a table. +/// +public class TableOptions { /// - /// Contains the options for configuring a table. + /// Gets or sets the name. /// - public class TableOptions - { - /// - /// Gets or sets the name. - /// - public string Name { get; set; } + public string Name { get; set; } - /// - /// Gets or sets the schema. - /// - public string Schema { get; set; } - } -} + /// + /// Gets or sets the schema. + /// + public string Schema { get; set; } +} \ No newline at end of file diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/Parbad.Storage.EntityFrameworkCore.csproj b/src/Parbad.Storage/EntityFrameworkCore/src/Parbad.Storage.EntityFrameworkCore.csproj index a536457f..75ef88b5 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/Parbad.Storage.EntityFrameworkCore.csproj +++ b/src/Parbad.Storage/EntityFrameworkCore/src/Parbad.Storage.EntityFrameworkCore.csproj @@ -2,9 +2,10 @@ Parbad.Storage.EntityFrameworkCore - 1.5.0 + 1.6.0 latest - net5;net6;net7;net8.0 + net5;net6;net7;net8.0;net9.0 + enable false true LGPL-3.0-or-later @@ -19,7 +20,7 @@ More information: https://github.com/Sina-Soltani/Parbad true Sina Soltani Parbad.Storage.EntityframeworkCore - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 diff --git a/src/Parbad.Storage/EntityFrameworkCore/src/Parbad.Storage.EntityframeworkCore.xml b/src/Parbad.Storage/EntityFrameworkCore/src/Parbad.Storage.EntityframeworkCore.xml index 1a0f2bfd..19462b5c 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/src/Parbad.Storage.EntityframeworkCore.xml +++ b/src/Parbad.Storage/EntityFrameworkCore/src/Parbad.Storage.EntityframeworkCore.xml @@ -89,7 +89,7 @@ - Parbad DbContext. + Gets Parbad DbContext. @@ -110,40 +110,30 @@ - - - EntityFramework Core implementation of . - - - - - Initializes an instance of . - - - - - - - + - + - + - + - + - - + + + EntityFramework Core implementation of . + - - + + + EntityFramework Core implementation of . + diff --git a/src/Parbad.Storage/EntityFrameworkCore/tests/EntityFrameworkStorageManagerTests.cs b/src/Parbad.Storage/EntityFrameworkCore/tests/EntityFrameworkStorageManagerTests.cs deleted file mode 100644 index 7d12083d..00000000 --- a/src/Parbad.Storage/EntityFrameworkCore/tests/EntityFrameworkStorageManagerTests.cs +++ /dev/null @@ -1,146 +0,0 @@ -using KellermanSoftware.CompareNetObjects; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; -using Parbad.Storage.Abstractions.Models; -using Parbad.Storage.EntityFrameworkCore.Context; -using Parbad.Storage.EntityFrameworkCore.Options; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Parbad.Storage.EntityFrameworkCore.Tests -{ - [TestClass] - public class EntityFrameworkStorageManagerTests - { - private ParbadDataContext _context; - private EntityFrameworkCoreStorageManager _storageManager; - - private static readonly Payment PaymentTestData = new Payment - { - TrackingNumber = 1, - Amount = 1, - Token = "token", - GatewayAccountName = "default", - GatewayName = "gateway", - IsCompleted = true, - IsPaid = false, - TransactionCode = "code" - }; - - [TestInitialize] - public void Setup() - { - var contextOptions = new DbContextOptionsBuilder() - .UseInMemoryDatabase(Guid.NewGuid().ToString()) - .Options; - - var efCoreOptions = new OptionsWrapper(new EntityFrameworkCoreOptions()); - - _context = new ParbadDataContext(contextOptions, efCoreOptions); - - var storage = new EntityFrameworkCoreStorage(_context); - - _storageManager = new EntityFrameworkCoreStorageManager(storage); - } - - [TestCleanup] - public ValueTask Cleanup() - { - return _context.DisposeAsync(); - } - - [TestMethod] - public async Task GetPaymentByTrackingNumber_Must_Be_Equal_With_Expected_Payment_Object() - { - await _storageManager.CreatePaymentAsync(PaymentTestData); - - var payment = await _storageManager.GetPaymentByTrackingNumberAsync(PaymentTestData.TrackingNumber); - - Assert.IsNotNull(payment); - - payment.ShouldCompare(PaymentTestData, "Payment is not equal with the expected Payment object."); - } - - [TestMethod] - public async Task GetPaymentByToken_Must_Be_Equal_With_Expected_Payment_Object() - { - await _storageManager.CreatePaymentAsync(PaymentTestData); - - var payment = await _storageManager.GetPaymentByTokenAsync(PaymentTestData.Token); - - Assert.IsNotNull(payment); - - payment.ShouldCompare(PaymentTestData, "Payment is not equal with the expected Payment object."); - } - - [TestMethod] - public async Task DoesPaymentExists_By_Token_Must_Be_True() - { - await _storageManager.CreatePaymentAsync(PaymentTestData); - - var exist = await _storageManager.DoesPaymentExistAsync(PaymentTestData.Token); - - Assert.IsTrue(exist); - } - - [TestMethod] - public async Task DoesPaymentExists_By_TrackingNumber_Must_Be_True() - { - await _storageManager.CreatePaymentAsync(PaymentTestData); - - var exist = await _storageManager.DoesPaymentExistAsync(PaymentTestData.TrackingNumber); - - Assert.IsTrue(exist); - } - - [TestMethod] - public async Task GetTransactions_Must_Be_Equal_With_Expected_Transactions() - { - await _storageManager.CreatePaymentAsync(PaymentTestData); - - var expectedTransactions = new List - { - new Transaction - { - Amount = 1000, - IsSucceed = false, - Message = "test", - Type = TransactionType.Request, - AdditionalData = "test", - PaymentId = PaymentTestData.Id - }, - new Transaction - { - Amount = 1000, - IsSucceed = false, - Message = "test", - Type = TransactionType.Verify, - AdditionalData = "test", - PaymentId = PaymentTestData.Id - }, - new Transaction - { - Amount = 1000, - IsSucceed = false, - Message = "test", - Type = TransactionType.Refund, - AdditionalData = "test", - PaymentId = PaymentTestData.Id - } - }; - - foreach (var transaction in expectedTransactions) - { - await _storageManager.CreateTransactionAsync(transaction); - } - - var transactions = await _storageManager.GetTransactionsAsync(PaymentTestData); - - Assert.IsNotNull(transactions); - Assert.AreEqual(expectedTransactions.Count, transactions.Count); - transactions.ShouldCompare(expectedTransactions); - } - } -} diff --git a/src/Parbad.Storage/EntityFrameworkCore/tests/Parbad.Storage.EntityFrameworkCore.Tests.csproj b/src/Parbad.Storage/EntityFrameworkCore/tests/Parbad.Storage.EntityFrameworkCore.Tests.csproj index de160849..67d0a9a2 100644 --- a/src/Parbad.Storage/EntityFrameworkCore/tests/Parbad.Storage.EntityFrameworkCore.Tests.csproj +++ b/src/Parbad.Storage/EntityFrameworkCore/tests/Parbad.Storage.EntityFrameworkCore.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 latest false diff --git a/src/Parbad/src/Abstraction/Invoice.cs b/src/Parbad/src/Abstraction/Invoice.cs index 914d9209..6b893864 100644 --- a/src/Parbad/src/Abstraction/Invoice.cs +++ b/src/Parbad/src/Abstraction/Invoice.cs @@ -3,50 +3,48 @@ using System.Collections.Generic; -namespace Parbad.Abstraction +namespace Parbad.Abstraction; + +/// +/// Describes an invoice which must be requested. +/// +public class Invoice { /// /// Describes an invoice which must be requested. /// - public class Invoice + public Invoice() { - /// - /// Describes an invoice which must be requested. - /// - public Invoice() - { - Properties = new Dictionary(); - } - - /// - /// Gets or sets the Tracking number of the invoice. - /// - public long TrackingNumber { get; set; } - - /// - /// Gets or sets the amount of the invoice. - /// Note: You can also enter long and decimal numbers. It can also be parsed to long and decimal. - /// Examples: - /// long a = invoice.Amount; - /// decimal a = invoice.Amount; - /// - public Money Amount { get; set; } - - /// - /// A complete URL of your website. It will be used by the gateway for redirecting - /// the client again to your website. - /// Note: A complete URL would be like: "http://www.mywebsite.com/foo/bar/" - /// - public CallbackUrl CallbackUrl { get; set; } - - /// - /// Gets or sets the name of the gateway which the invoice must be paid in. - /// - public string GatewayName { get; set; } - - /// - /// Gets or sets the properties of the invoice. - /// - public IDictionary Properties { get; set; } } + + /// + /// Gets or sets the Tracking number of the invoice. + /// + public long TrackingNumber { get; set; } + + /// + /// Gets or sets the amount of the invoice. + /// Note: You can also enter long and decimal numbers. It can also be parsed to long and decimal. + /// Examples: + /// long a = invoice.Amount; + /// decimal a = invoice.Amount; + /// + public Money Amount { get; set; } + + /// + /// A complete URL of your website. It will be used by the gateway for redirecting + /// the client again to your website. + /// Note: A complete URL would be like: "http://www.mywebsite.com/foo/bar/" + /// + public CallbackUrl CallbackUrl { get; set; } + + /// + /// Gets or sets the name of the gateway which the invoice must be paid in. + /// + public string GatewayName { get; set; } + + /// + /// Gets or sets the properties of the invoice. + /// + public IDictionary Properties { get; set; } = new Dictionary(); } diff --git a/src/Parbad/src/Gateway/ParbadVirtual/ParbadVirtualGatewayStyles.css b/src/Parbad/src/Gateway/ParbadVirtual/ParbadVirtualGatewayStyles.css index 32f955c8..77fcda9b 100644 --- a/src/Parbad/src/Gateway/ParbadVirtual/ParbadVirtualGatewayStyles.css +++ b/src/Parbad/src/Gateway/ParbadVirtual/ParbadVirtualGatewayStyles.css @@ -73,7 +73,7 @@ body { } .copyright { - margin-top: 20px; + margin: 20px 0 20px 0; color: white; font-size: .9rem; } diff --git a/src/Parbad/src/Gateway/Pasargad/Api/IPasargadApi.cs b/src/Parbad/src/Gateway/Pasargad/Api/IPasargadApi.cs new file mode 100644 index 00000000..c537e674 --- /dev/null +++ b/src/Parbad/src/Gateway/Pasargad/Api/IPasargadApi.cs @@ -0,0 +1,35 @@ +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; +using Parbad.Gateway.Pasargad.Api.Models; + +namespace Parbad.Gateway.Pasargad.Api; + +/// +/// API provided by Pasargad Bank. +/// +public interface IPasargadApi +{ + /// + /// Gets a token to start a payment request. + /// + Task GetToken(PasargadGetTokenRequestModel model, + string privateKey, + CancellationToken cancellationToken); + + /// + /// Verifies a payment. + /// + Task VerifyPayment(PasargadVerifyPaymentRequestModel model, + string privateKey, + CancellationToken cancellationToken); + + /// + /// Refunds an already paid invoice. + /// + Task RefundPayment(PasargadRefundPaymentRequestModel model, + string privateKey, + CancellationToken cancellationToken); +} diff --git a/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadGetTokenRequestModel.cs b/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadGetTokenRequestModel.cs new file mode 100644 index 00000000..271a5a6d --- /dev/null +++ b/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadGetTokenRequestModel.cs @@ -0,0 +1,34 @@ +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +using Newtonsoft.Json; + +namespace Parbad.Gateway.Pasargad.Api.Models; + +public class PasargadGetTokenRequestModel +{ + public string InvoiceNumber { get; set; } + + public string InvoiceDate { get; set; } + + public string TerminalCode { get; set; } + + public string MerchantCode { get; set; } + + public decimal Amount { get; set; } + + public string RedirectAddress { get; set; } + + public string Timestamp { get; set; } + + public string Action => "1003"; + + public string Mobile { get; set; } + + public string Email { get; set; } + + public string MerchantName { get; set; } + + [JsonProperty("PIDN")] + public string Pidn { get; set; } +} diff --git a/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadGetTokenResponseModel.cs b/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadGetTokenResponseModel.cs new file mode 100644 index 00000000..2ad51da7 --- /dev/null +++ b/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadGetTokenResponseModel.cs @@ -0,0 +1,13 @@ +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +namespace Parbad.Gateway.Pasargad.Api.Models; + +public class PasargadGetTokenResponseModel +{ + public bool IsSuccess { get; set; } + + public string Message { get; set; } + + public string Token { get; set; } +} diff --git a/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadRefundPaymentRequestModel.cs b/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadRefundPaymentRequestModel.cs new file mode 100644 index 00000000..9d63ff4f --- /dev/null +++ b/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadRefundPaymentRequestModel.cs @@ -0,0 +1,17 @@ +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +namespace Parbad.Gateway.Pasargad.Api.Models; + +public class PasargadRefundPaymentRequestModel +{ + public string InvoiceNumber { get; set; } + + public string InvoiceDate { get; set; } + + public string TerminalCode { get; set; } + + public string MerchantCode { get; set; } + + public string Timestamp { get; set; } +} diff --git a/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadRefundPaymentResponseModel.cs b/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadRefundPaymentResponseModel.cs new file mode 100644 index 00000000..f03b7620 --- /dev/null +++ b/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadRefundPaymentResponseModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +namespace Parbad.Gateway.Pasargad.Api.Models; + +public class PasargadRefundPaymentResponseModel +{ + public bool IsSuccess { get; set; } + + public string Message { get; set; } +} diff --git a/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadVerifyPaymentRequestModel.cs b/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadVerifyPaymentRequestModel.cs new file mode 100644 index 00000000..b37d5f70 --- /dev/null +++ b/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadVerifyPaymentRequestModel.cs @@ -0,0 +1,19 @@ +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +namespace Parbad.Gateway.Pasargad.Api.Models; + +public class PasargadVerifyPaymentRequestModel +{ + public string InvoiceNumber { get; set; } + + public string InvoiceDate { get; set; } + + public string TerminalCode { get; set; } + + public string MerchantCode { get; set; } + + public decimal Amount { get; set; } + + public string Timestamp { get; set; } +} diff --git a/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadVerifyPaymentResponseModel.cs b/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadVerifyPaymentResponseModel.cs new file mode 100644 index 00000000..9b90d73c --- /dev/null +++ b/src/Parbad/src/Gateway/Pasargad/Api/Models/PasargadVerifyPaymentResponseModel.cs @@ -0,0 +1,17 @@ +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +namespace Parbad.Gateway.Pasargad.Api.Models; + +public class PasargadVerifyPaymentResponseModel +{ + public bool IsSuccess { get; set; } + + public string Message { get; set; } + + public string MaskedCardNumber { get; set; } + + public string HashedCardNumber { get; set; } + + public string ShaparakRefNumber { get; set; } +} diff --git a/src/Parbad/src/Gateway/Pasargad/IPasargadCrypto.cs b/src/Parbad/src/Gateway/Pasargad/IPasargadCrypto.cs index 61d9bc1b..0550735e 100644 --- a/src/Parbad/src/Gateway/Pasargad/IPasargadCrypto.cs +++ b/src/Parbad/src/Gateway/Pasargad/IPasargadCrypto.cs @@ -1,7 +1,15 @@ -namespace Parbad.Gateway.Pasargad +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +namespace Parbad.Gateway.Pasargad; + +/// +/// An Encryptor to sign data which will be sent by each request to Pasargad gateway. +/// +public interface IPasargadCrypto { - public interface IPasargadCrypto - { - string Encrypt(string privateKey, string data); - } + /// + /// Encrypts the given data using the provided Private Key. + /// + string Encrypt(string privateKey, string data); } diff --git a/src/Parbad/src/Gateway/Pasargad/Internal/Models/PasargadCallbackResult.cs b/src/Parbad/src/Gateway/Pasargad/Internal/Models/PasargadCallbackResult.cs deleted file mode 100644 index d3c000ae..00000000 --- a/src/Parbad/src/Gateway/Pasargad/Internal/Models/PasargadCallbackResult.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Parbad. All rights reserved. -// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Parbad.Gateway.Pasargad.Internal.Models -{ - internal class PasargadCallbackResult - { - public bool IsSucceed { get; set; } - - /// - /// Equals to ReferenceID in Parbad system. - /// - public string InvoiceNumber { get; set; } - - public string InvoiceDate { get; set; } - - public string TransactionId { get; set; } - - public IEnumerable> CallbackCheckData { get; set; } - - public string Message { get; set; } - } -} diff --git a/src/Parbad/src/Gateway/Pasargad/Internal/Models/PasargadCallbackResultModel.cs b/src/Parbad/src/Gateway/Pasargad/Internal/Models/PasargadCallbackResultModel.cs new file mode 100644 index 00000000..3ac4df33 --- /dev/null +++ b/src/Parbad/src/Gateway/Pasargad/Internal/Models/PasargadCallbackResultModel.cs @@ -0,0 +1,17 @@ +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +namespace Parbad.Gateway.Pasargad.Internal.Models; + +internal class PasargadCallbackResultModel +{ + public bool IsSucceed => !string.IsNullOrWhiteSpace(InvoiceNumber) && + !string.IsNullOrWhiteSpace(InvoiceDate) && + !string.IsNullOrWhiteSpace(TransactionReferenceId); + + public string InvoiceNumber { get; set; } + + public string InvoiceDate { get; set; } + + public string TransactionReferenceId { get; set; } +} diff --git a/src/Parbad/src/Gateway/Pasargad/Internal/Models/PasargadCheckCallbackResult.cs b/src/Parbad/src/Gateway/Pasargad/Internal/Models/PasargadCheckCallbackResult.cs deleted file mode 100644 index 48bec106..00000000 --- a/src/Parbad/src/Gateway/Pasargad/Internal/Models/PasargadCheckCallbackResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Parbad. All rights reserved. -// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. - -using Parbad.Internal; - -namespace Parbad.Gateway.Pasargad.Internal.Models -{ - internal class PasargadCheckCallbackResult - { - public bool IsSucceed { get; set; } - - public PaymentVerifyResult Result { get; set; } - } -} diff --git a/src/Parbad/src/Gateway/Pasargad/Internal/PasargadApi.cs b/src/Parbad/src/Gateway/Pasargad/Internal/PasargadApi.cs new file mode 100644 index 00000000..c6a3881f --- /dev/null +++ b/src/Parbad/src/Gateway/Pasargad/Internal/PasargadApi.cs @@ -0,0 +1,73 @@ +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Parbad.Gateway.Pasargad.Api; +using Parbad.Gateway.Pasargad.Api.Models; +using Parbad.Net; + +namespace Parbad.Gateway.Pasargad.Internal; + +internal class PasargadApi : IPasargadApi +{ + private readonly HttpClient _httpClient; + private readonly PasargadGatewayOptions _options; + private readonly IPasargadCrypto _crypto; + + public PasargadApi(HttpClient httpClient, + IOptions options, + IPasargadCrypto crypto) + { + _httpClient = httpClient; + _crypto = crypto; + _options = options.Value; + } + + public Task GetToken(PasargadGetTokenRequestModel model, + string privateKey, + CancellationToken cancellationToken) + { + return PostJsonAsync(_options.ApiGetTokenUrl, model, privateKey, cancellationToken); + } + + public Task VerifyPayment(PasargadVerifyPaymentRequestModel model, + string privateKey, + CancellationToken cancellationToken) + { + return PostJsonAsync(_options.ApiVerificationUrl, model, privateKey, cancellationToken); + } + + public Task RefundPayment(PasargadRefundPaymentRequestModel model, + string privateKey, + CancellationToken cancellationToken) + { + return PostJsonAsync(_options.ApiRefundUrl, model, privateKey, cancellationToken); + } + + private Task PostJsonAsync(string url, + object request, + string privateKey, + CancellationToken cancellationToken) + { + var json = JsonConvert.SerializeObject(request); + + var sign = _crypto.Encrypt(privateKey, json); + + AddDefaultHeaders(sign); + + return _httpClient.PostJsonAsync(url, request, cancellationToken: cancellationToken); + } + + private void AddDefaultHeaders(string sign) + { + _httpClient.DefaultRequestHeaders.Accept.Clear(); + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + _httpClient.DefaultRequestHeaders.AddOrUpdate("Sign", sign); + } +} diff --git a/src/Parbad/src/Gateway/Pasargad/Internal/PasargadCrypto.cs b/src/Parbad/src/Gateway/Pasargad/Internal/PasargadCrypto.cs index 10e04c2e..8f7e6c39 100644 --- a/src/Parbad/src/Gateway/Pasargad/Internal/PasargadCrypto.cs +++ b/src/Parbad/src/Gateway/Pasargad/Internal/PasargadCrypto.cs @@ -1,26 +1,28 @@ -using System; +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +using System; using System.Security.Cryptography; using System.Text; using Parbad.Utilities; -namespace Parbad.Gateway.Pasargad.Internal +namespace Parbad.Gateway.Pasargad.Internal; + +internal class PasargadCrypto : IPasargadCrypto { - internal class PasargadCrypto : IPasargadCrypto + public string Encrypt(string privateKey, string data) { - public string Encrypt(string privateKey, string data) + using (var rsa = new RSACryptoServiceProvider()) { - using (var rsa = new RSACryptoServiceProvider()) - { - byte[] encryptedData; + byte[] encryptedData; #if NETSTANDARD2_0 rsa.FromXml(privateKey); encryptedData = rsa.SignData(Encoding.UTF8.GetBytes(data), HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); #else - rsa.FromXmlString(privateKey); - encryptedData = rsa.SignData(Encoding.UTF8.GetBytes(data), new SHA1CryptoServiceProvider()); + rsa.FromXmlString(privateKey); + encryptedData = rsa.SignData(Encoding.UTF8.GetBytes(data), new SHA1CryptoServiceProvider()); #endif - return Convert.ToBase64String(encryptedData); - } + return Convert.ToBase64String(encryptedData); } } } diff --git a/src/Parbad/src/Gateway/Pasargad/Internal/PasargadHelper.cs b/src/Parbad/src/Gateway/Pasargad/Internal/PasargadHelper.cs index 1c766d08..7e1df4f1 100644 --- a/src/Parbad/src/Gateway/Pasargad/Internal/PasargadHelper.cs +++ b/src/Parbad/src/Gateway/Pasargad/Internal/PasargadHelper.cs @@ -2,264 +2,36 @@ // Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; -using Parbad.Abstraction; using Parbad.Gateway.Pasargad.Internal.Models; using Parbad.Http; using Parbad.Internal; -using Parbad.Options; -using Parbad.Storage.Abstractions.Models; -using Parbad.Utilities; using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace Parbad.Gateway.Pasargad.Internal +namespace Parbad.Gateway.Pasargad.Internal; + +internal static class PasargadHelper { - internal static class PasargadHelper + public static async Task BindCallbackResultModel(HttpRequest httpRequest, + CancellationToken cancellationToken) { - private const string ActionNumber = "1003"; - private const string RefundNumber = "1004"; - - public static PaymentRequestResult CreateRequestResult( - Invoice invoice, - HttpContext httpContext, - PasargadGatewayAccount account, - IPasargadCrypto crypto, - PasargadGatewayOptions gatewayOptions) - { - var invoiceDate = GetTimeStamp(DateTime.Now); - - var timeStamp = invoiceDate; - - var dataToSign = string.Format("#{0}#{1}#{2}#{3}#{4}#{5}#{6}#{7}#", - account.MerchantCode, - account.TerminalCode, - invoice.TrackingNumber, - invoiceDate, - (long)invoice.Amount, - invoice.CallbackUrl, - ActionNumber, - timeStamp); - - var signedData = crypto.Encrypt(account.PrivateKey, dataToSign); - - var result = PaymentRequestResult.SucceedWithPost( - account.Name, - httpContext, - gatewayOptions.PaymentPageUrl, - new Dictionary - { - {"merchantCode", account.MerchantCode}, - {"terminalCode", account.TerminalCode}, - {"invoiceNumber", invoice.TrackingNumber.ToString()}, - {"invoiceDate", invoiceDate}, - {"amount", invoice.Amount.ToLongString()}, - {"redirectAddress", invoice.CallbackUrl}, - {"action", ActionNumber}, - {"timeStamp", timeStamp}, - {"sign", signedData} - }); - - result.DatabaseAdditionalData.Add("timeStamp", timeStamp); - - return result; - } - - public static async Task CreateCallbackResult( - HttpRequest httpRequest, - MessagesOptions messagesOptions, - CancellationToken cancellationToken) - { - // Reference ID - var invoiceNumber = await httpRequest.TryGetParamAsync("iN", cancellationToken).ConfigureAwaitFalse(); - - // Invoice Date - var invoiceDate = await httpRequest.TryGetParamAsync("iD", cancellationToken).ConfigureAwaitFalse(); - - // Transaction Code - var transactionId = await httpRequest.TryGetParamAsync("tref", cancellationToken).ConfigureAwaitFalse(); - - var isSucceed = true; - string message = null; - - if (string.IsNullOrWhiteSpace(invoiceNumber.Value) || - string.IsNullOrWhiteSpace(invoiceDate.Value) || - string.IsNullOrWhiteSpace(transactionId.Value)) - { - isSucceed = false; - - message = messagesOptions.InvalidDataReceivedFromGateway; - } - - var data = new[] { new KeyValuePair("invoiceUID", transactionId.Value) }; - - return new PasargadCallbackResult - { - IsSucceed = isSucceed, - InvoiceNumber = invoiceNumber.Value, - InvoiceDate = invoiceDate.Value, - TransactionId = transactionId.Value, - CallbackCheckData = data, - Message = message - }; - } - - public static PasargadCheckCallbackResult CreateCheckCallbackResult(string webServiceResponse, PasargadGatewayAccount account, PasargadCallbackResult callbackResult, MessagesOptions messagesOptions) - { - var compareReferenceId = XmlHelper.GetNodeValueFromXml(webServiceResponse, "invoiceNumber"); - var compareAction = XmlHelper.GetNodeValueFromXml(webServiceResponse, "action"); - var compareMerchantCode = XmlHelper.GetNodeValueFromXml(webServiceResponse, "merchantCode"); - var compareTerminalCode = XmlHelper.GetNodeValueFromXml(webServiceResponse, "terminalCode"); - - bool isSucceed; - PaymentVerifyResult verifyResult = null; - - if (compareReferenceId.IsNullOrWhiteSpace() || - compareAction.IsNullOrWhiteSpace() || - compareMerchantCode.IsNullOrWhiteSpace() || - compareTerminalCode.IsNullOrWhiteSpace()) - { - isSucceed = false; - - verifyResult = PaymentVerifyResult.Failed(messagesOptions.InvalidDataReceivedFromGateway); - } - else - { - var responseResult = XmlHelper.GetNodeValueFromXml(webServiceResponse, "result"); - - isSucceed = responseResult.Equals("true", StringComparison.OrdinalIgnoreCase) && - compareReferenceId == callbackResult.InvoiceNumber && - compareAction == ActionNumber && - compareMerchantCode == account.MerchantCode && - compareTerminalCode == account.TerminalCode; + var invoiceNumber = await httpRequest.TryGetParamAsync("iN", cancellationToken).ConfigureAwaitFalse(); - if (!isSucceed) - { - verifyResult = PaymentVerifyResult.Failed("پرداخت موفقيت آميز نبود و يا توسط خريدار کنسل شده است"); - } - } + var invoiceDate = await httpRequest.TryGetParamAsync("iD", cancellationToken).ConfigureAwaitFalse(); - return new PasargadCheckCallbackResult - { - IsSucceed = isSucceed, - Result = verifyResult - }; - } + var transactionReferenceId = await httpRequest.TryGetParamAsync("tref", cancellationToken).ConfigureAwaitFalse(); - public static IEnumerable> CreateVerifyData( - InvoiceContext context, - PasargadGatewayAccount account, - IPasargadCrypto crypto, - PasargadCallbackResult callbackResult) - { - var timeStamp = GetTimeStamp(DateTime.Now); - - var dataToSign = string.Format("#{0}#{1}#{2}#{3}#{4}#{5}#", - account.MerchantCode, - account.TerminalCode, - context.Payment.TrackingNumber, - callbackResult.InvoiceDate, - (long)context.Payment.Amount, - timeStamp); - - var signData = crypto.Encrypt(account.PrivateKey, dataToSign); - - return new[] - { - new KeyValuePair("InvoiceNumber", context.Payment.TrackingNumber.ToString()), - new KeyValuePair("InvoiceDate", callbackResult.InvoiceDate), - new KeyValuePair("MerchantCode", account.MerchantCode), - new KeyValuePair("TerminalCode", account.TerminalCode), - new KeyValuePair("Amount", ((long)context.Payment.Amount).ToString()), - new KeyValuePair("TimeStamp", timeStamp), - new KeyValuePair("Sign", signData) - }; - } - - public static PaymentVerifyResult CreateVerifyResult(string webServiceResponse, PasargadCallbackResult callbackResult, MessagesOptions messagesOptions) - { - var result = XmlHelper.GetNodeValueFromXml(webServiceResponse, "result"); - - var isSucceed = result.Equals("true", StringComparison.OrdinalIgnoreCase); - - var message = isSucceed - ? messagesOptions.PaymentSucceed - : XmlHelper.GetNodeValueFromXml(webServiceResponse, "resultMessage"); - - return new PaymentVerifyResult - { - Status = isSucceed ? PaymentVerifyResultStatus.Succeed : PaymentVerifyResultStatus.Failed, - TransactionCode = callbackResult.TransactionId, - Message = message - }; - } - - public static IEnumerable> CreateRefundData( - InvoiceContext context, - Money amount, - IPasargadCrypto crypto, - PasargadGatewayAccount account) - { - var transactionRecord = context.Transactions.FirstOrDefault(transaction => transaction.Type == TransactionType.Request); - - if (transactionRecord == null) - { - throw new Exception($"Cannot find transaction record for Payment-{context.Payment.TrackingNumber}"); - } - - if (!AdditionalDataConverter.ToDictionary(transactionRecord).TryGetValue("invoiceDate", out var invoiceDate)) - { - throw new Exception("Cannot get the invoiceDate from database."); - } - - var timeStamp = GetTimeStamp(DateTime.Now); - - var dataToSign = string.Format("#{0}#{1}#{2}#{3}#{4}#{5}#{6}#", - account.MerchantCode, - account.TerminalCode, - context.Payment.TrackingNumber, - invoiceDate, - (long)amount, - RefundNumber, - timeStamp); - - var signedData = crypto.Encrypt(account.PrivateKey, dataToSign); - - return new[] - { - new KeyValuePair("InvoiceNumber", context.Payment.TrackingNumber.ToString()), - new KeyValuePair("InvoiceDate", invoiceDate), - new KeyValuePair("MerchantCode", account.MerchantCode), - new KeyValuePair("TerminalCode", account.TerminalCode), - new KeyValuePair("Amount", amount.ToLongString()), - new KeyValuePair("action", RefundNumber), - new KeyValuePair("TimeStamp", timeStamp), - new KeyValuePair("Sign", signedData) - }; - } - - public static PaymentRefundResult CreateRefundResult(string webServiceResponse, MessagesOptions messagesOptions) - { - var result = XmlHelper.GetNodeValueFromXml(webServiceResponse, "result"); - - var isSucceed = result.Equals("true", StringComparison.OrdinalIgnoreCase); - - var message = isSucceed - ? messagesOptions.PaymentSucceed - : XmlHelper.GetNodeValueFromXml(webServiceResponse, "resultMessage"); - - return new PaymentRefundResult - { - Status = isSucceed ? PaymentRefundResultStatus.Succeed : PaymentRefundResultStatus.Failed, - Message = message - }; - } + return new PasargadCallbackResultModel + { + InvoiceNumber = invoiceNumber.Value, + InvoiceDate = invoiceDate.Value, + TransactionReferenceId = transactionReferenceId.Value + }; + } - private static string GetTimeStamp(DateTime dateTime) - { - return dateTime.ToString("yyyy/MM/dd HH:mm:ss"); - } + public static string GetTimeStamp() + { + return DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"); } } diff --git a/src/Parbad/src/Gateway/Pasargad/PasargadGateway.cs b/src/Parbad/src/Gateway/Pasargad/PasargadGateway.cs index c122e1ea..13ab0034 100644 --- a/src/Parbad/src/Gateway/Pasargad/PasargadGateway.cs +++ b/src/Parbad/src/Gateway/Pasargad/PasargadGateway.cs @@ -7,138 +7,188 @@ using Parbad.Gateway.Pasargad.Internal; using Parbad.GatewayBuilders; using Parbad.Internal; -using Parbad.Net; using Parbad.Options; using System; -using System.Net.Http; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Parbad.Gateway.Pasargad.Api; +using Parbad.Gateway.Pasargad.Api.Models; +using Parbad.Storage.Abstractions.Models; -namespace Parbad.Gateway.Pasargad +namespace Parbad.Gateway.Pasargad; + +[Gateway(Name)] +public class PasargadGateway : GatewayBase { - [Gateway(Name)] - public class PasargadGateway : GatewayBase + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IPasargadApi _pasargadApi; + private readonly PasargadGatewayOptions _gatewayOptions; + private readonly MessagesOptions _messageOptions; + + private const string InvoiceDateKey = "invoiceDate"; + public const string Name = "Pasargad"; + + public PasargadGateway(IHttpContextAccessor httpContextAccessor, + IPasargadApi pasargadApi, + IGatewayAccountProvider accountProvider, + IOptions gatewayOptions, + IOptions messageOptions) + : base(accountProvider) { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly HttpClient _httpClient; - private readonly IPasargadCrypto _crypto; - private readonly PasargadGatewayOptions _gatewayOptions; - private readonly IOptions _messageOptions; - - public const string Name = "Pasargad"; - - public PasargadGateway( - IHttpContextAccessor httpContextAccessor, - IHttpClientFactory httpClientFactory, - IGatewayAccountProvider accountProvider, - IPasargadCrypto crypto, - IOptions gatewayOptions, - IOptions messageOptions) : base(accountProvider) - { - _httpContextAccessor = httpContextAccessor; - _httpClient = httpClientFactory.CreateClient(this); - _crypto = crypto; - _gatewayOptions = gatewayOptions.Value; - _messageOptions = messageOptions; - } + _httpContextAccessor = httpContextAccessor; + _pasargadApi = pasargadApi; + _gatewayOptions = gatewayOptions.Value; + _messageOptions = messageOptions.Value; + } - /// - public override async Task RequestAsync(Invoice invoice, CancellationToken cancellationToken = default) + /// + public override async Task RequestAsync(Invoice invoice, CancellationToken cancellationToken = default) + { + if (invoice == null) throw new ArgumentNullException(nameof(invoice)); + + var account = await GetAccountAsync(invoice).ConfigureAwaitFalse(); + + var invoiceDate = PasargadHelper.GetTimeStamp(); + var timeStamp = invoiceDate; + + var additionalData = invoice.GetPasargadRequestAdditionalData(); + + var response = await _pasargadApi.GetToken(new PasargadGetTokenRequestModel + { + MerchantCode = account.MerchantCode, + TerminalCode = account.TerminalCode, + InvoiceNumber = invoice.TrackingNumber.ToString(), + InvoiceDate = invoiceDate, + Amount = invoice.Amount, + RedirectAddress = invoice.CallbackUrl, + Timestamp = timeStamp, + Email = additionalData?.Email, + Mobile = additionalData?.Mobile, + Pidn = additionalData?.Pidn, + MerchantName = additionalData?.MerchantName + }, + account.PrivateKey, + cancellationToken) + .ConfigureAwaitFalse(); + + if (!response.IsSuccess) { - if (invoice == null) throw new ArgumentNullException(nameof(invoice)); + return PaymentRequestResult.Failed(response.Message, account.Name); + } - var account = await GetAccountAsync(invoice).ConfigureAwaitFalse(); + var form = new Dictionary + { + { "Token", response.Token } + }; - return PasargadHelper.CreateRequestResult(invoice, _httpContextAccessor.HttpContext, account, _crypto, _gatewayOptions); - } + var result = PaymentRequestResult.SucceedWithPost(account.Name, + _httpContextAccessor.HttpContext, + _gatewayOptions.PaymentPageUrl, + form); - /// - public override async Task FetchAsync(InvoiceContext context, CancellationToken cancellationToken = default) - { - if (context == null) throw new ArgumentNullException(nameof(context)); + result.DatabaseAdditionalData.Add(InvoiceDateKey, invoiceDate); - var callbackResult = await PasargadHelper.CreateCallbackResult( - _httpContextAccessor.HttpContext.Request, - _messageOptions.Value, - cancellationToken) - .ConfigureAwaitFalse(); + return result; + } - if (callbackResult.IsSucceed) - { - return PaymentFetchResult.ReadyForVerifying(); - } + /// + public override async Task FetchAsync(InvoiceContext context, CancellationToken cancellationToken = default) + { + if (context == null) throw new ArgumentNullException(nameof(context)); - return PaymentFetchResult.Failed(callbackResult.Message); - } + var callbackResult = await PasargadHelper.BindCallbackResultModel(_httpContextAccessor.HttpContext.Request, + cancellationToken) + .ConfigureAwaitFalse(); - /// - public override async Task VerifyAsync(InvoiceContext context, CancellationToken cancellationToken = default) + if (!callbackResult.IsSucceed) { - if (context == null) throw new ArgumentNullException(nameof(context)); - - var callbackResult = await PasargadHelper.CreateCallbackResult( - _httpContextAccessor.HttpContext.Request, - _messageOptions.Value, - cancellationToken) - .ConfigureAwaitFalse(); + return PaymentFetchResult.Failed(_messageOptions.PaymentFailed); + } - if (!callbackResult.IsSucceed) - { - return PaymentVerifyResult.Failed(callbackResult.Message); - } + var result = PaymentFetchResult.ReadyForVerifying(); + result.TransactionCode = callbackResult.TransactionReferenceId; - var responseMessage = await _httpClient.PostFormAsync( - _gatewayOptions.ApiCheckPaymentUrl, - callbackResult.CallbackCheckData, - cancellationToken) - .ConfigureAwaitFalse(); + return result; + } - var response = await responseMessage.Content.ReadAsStringAsync().ConfigureAwaitFalse(); + /// + public override async Task VerifyAsync(InvoiceContext context, CancellationToken cancellationToken = default) + { + if (context == null) throw new ArgumentNullException(nameof(context)); - var account = await GetAccountAsync(context.Payment).ConfigureAwaitFalse(); + var callbackResult = await PasargadHelper.BindCallbackResultModel(_httpContextAccessor.HttpContext.Request, + cancellationToken) + .ConfigureAwaitFalse(); - var checkCallbackResult = PasargadHelper.CreateCheckCallbackResult( - response, - account, - callbackResult, - _messageOptions.Value); + if (!callbackResult.IsSucceed) + { + return PaymentVerifyResult.Failed(_messageOptions.PaymentFailed); + } - if (!checkCallbackResult.IsSucceed) - { - return checkCallbackResult.Result; - } + var account = await GetAccountAsync(context.Payment).ConfigureAwaitFalse(); + + var response = await _pasargadApi.VerifyPayment(new PasargadVerifyPaymentRequestModel + { + MerchantCode = account.MerchantCode, + TerminalCode = account.TerminalCode, + InvoiceNumber = context.Payment.TrackingNumber.ToString(), + InvoiceDate = callbackResult.InvoiceDate, + Amount = context.Payment.Amount, + Timestamp = PasargadHelper.GetTimeStamp() + }, + account.PrivateKey, + cancellationToken) + .ConfigureAwaitFalse(); + + if (!response.IsSuccess) + { + return PaymentVerifyResult.Failed(response.Message ?? _messageOptions.PaymentFailed); + } - var data = PasargadHelper.CreateVerifyData(context, account, _crypto, callbackResult); + return PaymentVerifyResult.Succeed(callbackResult.TransactionReferenceId, + _messageOptions.PaymentSucceed); + } - responseMessage = await _httpClient.PostFormAsync( - _gatewayOptions.ApiVerificationUrl, - data, - cancellationToken) - .ConfigureAwaitFalse(); + /// + public override async Task RefundAsync(InvoiceContext context, Money amount, CancellationToken cancellationToken = default) + { + if (context == null) throw new ArgumentNullException(nameof(context)); - response = await responseMessage.Content.ReadAsStringAsync().ConfigureAwaitFalse(); + var account = await GetAccountAsync(context.Payment).ConfigureAwaitFalse(); - return PasargadHelper.CreateVerifyResult(response, callbackResult, _messageOptions.Value); - } + var requestTransaction = context.Transactions.SingleOrDefault(transaction => transaction.Type == TransactionType.Request); - /// - public override async Task RefundAsync(InvoiceContext context, Money amount, CancellationToken cancellationToken = default) + if (requestTransaction == null) { - if (context == null) throw new ArgumentNullException(nameof(context)); - - var account = await GetAccountAsync(context.Payment).ConfigureAwaitFalse(); - - var data = PasargadHelper.CreateRefundData(context, amount, _crypto, account); + return PaymentRefundResult.Failed($"Transaction for Invoice {context.Payment.TrackingNumber} not found"); + } - var responseMessage = await _httpClient.PostFormAsync( - _gatewayOptions.ApiRefundUrl, - data, - cancellationToken) - .ConfigureAwaitFalse(); + var requestTransactionAdditionalData = requestTransaction.ToDictionary(); - var response = await responseMessage.Content.ReadAsStringAsync().ConfigureAwaitFalse(); + if (!requestTransactionAdditionalData.TryGetValue(InvoiceDateKey, out var invoiceDate)) + { + return PaymentRefundResult.Failed($"InvoiceDate for Invoice {context.Payment.TrackingNumber} not found"); + } - return PasargadHelper.CreateRefundResult(response, _messageOptions.Value); + var response = await _pasargadApi.RefundPayment(new PasargadRefundPaymentRequestModel + { + MerchantCode = account.MerchantCode, + TerminalCode = account.TerminalCode, + InvoiceNumber = context.Payment.TrackingNumber.ToString(), + InvoiceDate = invoiceDate, + Timestamp = PasargadHelper.GetTimeStamp() + }, + account.PrivateKey, + cancellationToken) + .ConfigureAwaitFalse(); + + if (!response.IsSuccess) + { + return PaymentRefundResult.Failed(response.Message ?? "Refund failed."); } + + return PaymentRefundResult.Succeed(); } } diff --git a/src/Parbad/src/Gateway/Pasargad/PasargadGatewayAccount.cs b/src/Parbad/src/Gateway/Pasargad/PasargadGatewayAccount.cs index 5dbf52cf..be75398c 100644 --- a/src/Parbad/src/Gateway/Pasargad/PasargadGatewayAccount.cs +++ b/src/Parbad/src/Gateway/Pasargad/PasargadGatewayAccount.cs @@ -3,14 +3,13 @@ using Parbad.Abstraction; -namespace Parbad.Gateway.Pasargad +namespace Parbad.Gateway.Pasargad; + +public class PasargadGatewayAccount : GatewayAccount { - public class PasargadGatewayAccount : GatewayAccount - { - public string MerchantCode { get; set; } + public string MerchantCode { get; set; } - public string TerminalCode { get; set; } + public string TerminalCode { get; set; } - public string PrivateKey { get; set; } - } + public string PrivateKey { get; set; } } diff --git a/src/Parbad/src/Gateway/Pasargad/PasargadGatewayBuilderExtensions.cs b/src/Parbad/src/Gateway/Pasargad/PasargadGatewayBuilderExtensions.cs index 5205f149..d9051b88 100644 --- a/src/Parbad/src/Gateway/Pasargad/PasargadGatewayBuilderExtensions.cs +++ b/src/Parbad/src/Gateway/Pasargad/PasargadGatewayBuilderExtensions.cs @@ -2,56 +2,117 @@ // Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. using System; +using System.Net.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Parbad.Gateway.Pasargad.Api; using Parbad.Gateway.Pasargad.Internal; using Parbad.GatewayBuilders; -namespace Parbad.Gateway.Pasargad +namespace Parbad.Gateway.Pasargad; + +public static class PasargadGatewayBuilderExtensions { - public static class PasargadGatewayBuilderExtensions + /// + /// Adds Pasargad gateway to Parbad services. + /// + /// + public static IGatewayConfigurationBuilder AddPasargad(this IGatewayBuilder builder) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + return builder + .AddGateway() + .WithHttpClient((serviceProvider, httpClient) => + { + var gatewayOptions = serviceProvider.GetRequiredService>(); + + httpClient.BaseAddress = new Uri(gatewayOptions.Value.ApiBaseUrl); + }) + .WithEncryptor(ServiceLifetime.Transient) + .WithOptions(options => { }); + } + + /// + /// Configures the HttpClient for . + /// + /// Implementation type of . + /// + public static IGatewayConfigurationBuilder WithHttpClient(this IGatewayConfigurationBuilder builder, + Action configureHttpClient, + Action configureHttpClientBuilder = null) + where TGatewayApi : class, IPasargadApi + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (configureHttpClient == null) throw new ArgumentNullException(nameof(configureHttpClient)); + + builder.WithHttpClient(configureHttpClient, configureHttpClientBuilder); + + return builder; + } + + /// + /// Configures the accounts for . + /// + /// + /// Configures the accounts. + public static IGatewayConfigurationBuilder WithAccounts( + this IGatewayConfigurationBuilder builder, + Action> configureAccounts) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + return builder.WithAccounts(configureAccounts); + } + + /// + /// Configures the options for Pasargad Gateway. + /// + /// + /// Configuration + public static IGatewayConfigurationBuilder WithOptions( + this IGatewayConfigurationBuilder builder, + Action configureOptions) + { + builder.Services.Configure(configureOptions); + + return builder; + } + + /// + /// Registers an Encryptor for . + /// + public static IGatewayConfigurationBuilder WithEncryptor( + this IGatewayConfigurationBuilder builder, + IPasargadCrypto crypto) + { + builder.Services.AddOrUpdate(crypto); + + return builder; + } + + /// + /// Registers an Encryptor for . + /// + public static IGatewayConfigurationBuilder WithEncryptor( + this IGatewayConfigurationBuilder builder, + ServiceLifetime lifetime) + where TEncryptor : class, IPasargadCrypto { - /// - /// Adds Pasargad gateway to Parbad services. - /// - /// - public static IGatewayConfigurationBuilder AddPasargad(this IGatewayBuilder builder) - { - if (builder == null) throw new ArgumentNullException(nameof(builder)); - - builder.Services.AddSingleton(); - - return builder - .AddGateway() - .WithHttpClient(clientBuilder => { }) - .WithOptions(options => { }); - } - - /// - /// Configures the accounts for . - /// - /// - /// Configures the accounts. - public static IGatewayConfigurationBuilder WithAccounts( - this IGatewayConfigurationBuilder builder, - Action> configureAccounts) - { - if (builder == null) throw new ArgumentNullException(nameof(builder)); - - return builder.WithAccounts(configureAccounts); - } - - /// - /// Configures the options for Pasargad Gateway. - /// - /// - /// Configuration - public static IGatewayConfigurationBuilder WithOptions( - this IGatewayConfigurationBuilder builder, - Action configureOptions) - { - builder.Services.Configure(configureOptions); - - return builder; - } + builder.Services.AddOrUpdate(lifetime); + + return builder; + } + + /// + /// Registers an Encryptor for . + /// + public static IGatewayConfigurationBuilder WithEncryptor( + this IGatewayConfigurationBuilder builder, + Func setup) + { + builder.Services.AddOrUpdate(setup); + + return builder; } } diff --git a/src/Parbad/src/Gateway/Pasargad/PasargadGatewayInvoiceBuilderExtensions.cs b/src/Parbad/src/Gateway/Pasargad/PasargadGatewayInvoiceBuilderExtensions.cs index 746d6bdf..a9fea50e 100644 --- a/src/Parbad/src/Gateway/Pasargad/PasargadGatewayInvoiceBuilderExtensions.cs +++ b/src/Parbad/src/Gateway/Pasargad/PasargadGatewayInvoiceBuilderExtensions.cs @@ -1,19 +1,48 @@ -using System; +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +using System; +using Parbad.Abstraction; using Parbad.InvoiceBuilder; -namespace Parbad.Gateway.Pasargad +namespace Parbad.Gateway.Pasargad; + +public static class PasargadGatewayInvoiceBuilderExtensions { - public static class PasargadGatewayInvoiceBuilderExtensions + private const string RequestAdditionalDataKey = nameof(RequestAdditionalDataKey); + + /// + /// The invoice will be sent to Pasargad gateway. + /// + /// + public static IInvoiceBuilder UsePasargad(this IInvoiceBuilder builder) { - /// - /// The invoice will be sent to Pasargad gateway. - /// - /// - public static IInvoiceBuilder UsePasargad(this IInvoiceBuilder builder) - { - if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + return builder.SetGateway(PasargadGateway.Name); + } + + /// + /// Sets additional data that will be sent to Pasargad gateway when requesting a token. + /// + /// + public static IInvoiceBuilder SetPasargadData(this IInvoiceBuilder builder, PasargadRequestAdditionalData additionalData) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (additionalData == null) throw new ArgumentNullException(nameof(additionalData)); + + return builder.AddOrUpdateProperty(RequestAdditionalDataKey, additionalData); + } - return builder.SetGateway(PasargadGateway.Name); + internal static PasargadRequestAdditionalData GetPasargadRequestAdditionalData(this Invoice invoice) + { + if (invoice == null) throw new ArgumentNullException(nameof(invoice)); + + if (!invoice.Properties.TryGetValue(RequestAdditionalDataKey, out var additionalData)) + { + return null; } + + return (PasargadRequestAdditionalData)additionalData; } } diff --git a/src/Parbad/src/Gateway/Pasargad/PasargadGatewayOptions.cs b/src/Parbad/src/Gateway/Pasargad/PasargadGatewayOptions.cs index e0197bcf..fbcd6f2a 100644 --- a/src/Parbad/src/Gateway/Pasargad/PasargadGatewayOptions.cs +++ b/src/Parbad/src/Gateway/Pasargad/PasargadGatewayOptions.cs @@ -1,13 +1,17 @@ -namespace Parbad.Gateway.Pasargad +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +namespace Parbad.Gateway.Pasargad; + +public class PasargadGatewayOptions { - public class PasargadGatewayOptions - { - public string PaymentPageUrl { get; set; } = "https://pep.shaparak.ir/gateway.aspx"; + public string PaymentPageUrl { get; set; } = "https://pep.shaparak.ir/payment.aspx"; + + public string ApiBaseUrl { get; set; } = "https://pep.shaparak.ir/Api/v1/"; - public string ApiCheckPaymentUrl { get; set; } = "https://pep.shaparak.ir/CheckTransactionResult.aspx"; + public string ApiGetTokenUrl { get; set; } = "Payment/GetToken"; - public string ApiVerificationUrl { get; set; } = "https://pep.shaparak.ir/VerifyPayment.aspx"; + public string ApiVerificationUrl { get; set; } = "Payment/VerifyPayment"; - public string ApiRefundUrl { get; set; } = "https://pep.shaparak.ir/DoRefund.aspx"; - } + public string ApiRefundUrl { get; set; } = "Payment/RefundPayment"; } diff --git a/src/Parbad/src/Gateway/Pasargad/PasargadRequestAdditionalData.cs b/src/Parbad/src/Gateway/Pasargad/PasargadRequestAdditionalData.cs new file mode 100644 index 00000000..5dd31a7e --- /dev/null +++ b/src/Parbad/src/Gateway/Pasargad/PasargadRequestAdditionalData.cs @@ -0,0 +1,18 @@ +// Copyright (c) Parbad. All rights reserved. +// Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. + +namespace Parbad.Gateway.Pasargad; + +/// +/// Additional Data that can be sent to Pasargad gateway when requesting a token. +/// +public class PasargadRequestAdditionalData +{ + public string Mobile { get; set; } + + public string Email { get; set; } + + public string MerchantName { get; set; } + + public string Pidn { get; set; } +} diff --git a/src/Parbad/src/Gateway/Saman/Internal/Models/SamanTokenRequest.cs b/src/Parbad/src/Gateway/Saman/Internal/Models/SamanTokenRequest.cs index 086e5ad5..63ca1fa7 100644 --- a/src/Parbad/src/Gateway/Saman/Internal/Models/SamanTokenRequest.cs +++ b/src/Parbad/src/Gateway/Saman/Internal/Models/SamanTokenRequest.cs @@ -16,7 +16,7 @@ internal class SamanTokenRequest public string ResNum { get; set; } - public CallbackUrl RedirectUrl { get; set; } + public string RedirectUrl { get; set; } public string CellNumber { get; set; } } diff --git a/src/Parbad/src/Gateway/Saman/Internal/SamanHelper.cs b/src/Parbad/src/Gateway/Saman/Internal/SamanHelper.cs index 2f6f3ee6..105a29df 100644 --- a/src/Parbad/src/Gateway/Saman/Internal/SamanHelper.cs +++ b/src/Parbad/src/Gateway/Saman/Internal/SamanHelper.cs @@ -21,6 +21,8 @@ namespace Parbad.Gateway.Saman.Internal; internal static class SamanHelper { private const string RefNumKey = nameof(RefNumKey); + private const string CallbackSuccessCode = "2"; + private const int VerificationSuccessCode = 0; public const string AdditionalVerificationDataKey = "SamanAdditionalVerificationData"; public const string CellNumberPropertyKey = "SamanCellNumber"; @@ -98,40 +100,6 @@ public static async Task BindCallbackResponse(HttpRequest SecurePan = securePan.Value, HashedCardNumber = hashedCardNumber.Value, }; - - // var status = await httpRequest.TryGetParamAsync("status", cancellationToken).ConfigureAwaitFalse(); - // - // if (!status.Exists || status.Value.IsNullOrEmpty()) - // { - // message = messagesOptions.InvalidDataReceivedFromGateway; - // } - // else - // { - // var referenceIdResult = await httpRequest.TryGetParamAsync("ResNum", cancellationToken).ConfigureAwaitFalse(); - // if (referenceIdResult.Exists) referenceId = referenceIdResult.Value; - // - // var transactionIdResult = await httpRequest.TryGetParamAsync("RefNum", cancellationToken).ConfigureAwaitFalse(); - // if (transactionIdResult.Exists) transactionId = transactionIdResult.Value; - // - // isSuccess = status.Value.Equals("OK", StringComparison.OrdinalIgnoreCase); - // - // if (!isSuccess) - // { - // message = SamanStateTranslator.Translate(status.Value, messagesOptions); - // } - // } - // - // return new SamanCallbackResult - // { - // IsSucceed = isSuccess, - // ReferenceId = referenceId, - // TransactionId = transactionId, - // SecurePan = securePan.Value, - // Cid = cid.Value, - // TraceNo = traceNo.Value, - // Rrn = rrn.Value, - // Message = message - // }; } public static IPaymentFetchResult CreateFetchResult(SamanCallbackResponse callbackResponse, @@ -141,7 +109,7 @@ public static IPaymentFetchResult CreateFetchResult(SamanCallbackResponse callba { var isCallbackResponseValid = ValidateCallbackResponse(callbackResponse, invoiceContext, gatewayAccount, out var message); - var isReceivedSuccessFromGateway = callbackResponse.Status == "2"; + var isReceivedSuccessFromGateway = callbackResponse.Status == CallbackSuccessCode; var isSucceed = isCallbackResponseValid && isReceivedSuccessFromGateway; @@ -184,9 +152,9 @@ public static PaymentVerifyResult CreateVerifyResult(SamanGatewayAccount gateway { var isSuccess = verificationResponse.TransactionDetail.TerminalNumber.ToString() == gatewayAccount.TerminalId && verificationResponse.TransactionDetail.AffectiveAmount == (long)invoiceContext.Payment.Amount && - verificationResponse.TransactionDetail.RefNum == callbackResponse.RefNum; - - + verificationResponse.TransactionDetail.RefNum == callbackResponse.RefNum && + verificationResponse.ResultCode == VerificationSuccessCode; + var message = isSuccess ? messagesOptions.PaymentSucceed : SamanResultTranslator.Translate(verificationResponse.ResultCode.ToString(), messagesOptions); @@ -213,7 +181,8 @@ public static SamanReverseRequest CreateReverseRequest(InvoiceContext context, S if (string.IsNullOrEmpty(verificationTransaction?.AdditionalData) || !verificationTransaction.ToDictionary().ContainsKey(RefNumKey)) { - throw new InvalidOperationException($"No Transaction of type Verification or additional data found for reversing the invoice {context.Payment.TrackingNumber}."); + throw new + InvalidOperationException($"No Transaction of type Verification or additional data found for reversing the invoice {context.Payment.TrackingNumber}."); } return new() diff --git a/src/Parbad/src/GatewayBuilders/IGatewayBuilder.cs b/src/Parbad/src/GatewayBuilders/IGatewayBuilder.cs index fa43551d..f68fedfa 100644 --- a/src/Parbad/src/GatewayBuilders/IGatewayBuilder.cs +++ b/src/Parbad/src/GatewayBuilders/IGatewayBuilder.cs @@ -4,25 +4,24 @@ using Microsoft.Extensions.DependencyInjection; using Parbad.Abstraction; -namespace Parbad.GatewayBuilders +namespace Parbad.GatewayBuilders; + +/// +/// A builder for building a gateway. +/// +public interface IGatewayBuilder { /// - /// A builder for building a gateway. + /// Specifies the contract for a collection of service descriptors. /// - public interface IGatewayBuilder - { - /// - /// Specifies the contract for a collection of service descriptors. - /// - IServiceCollection Services { get; } + IServiceCollection Services { get; } - /// - /// Adds the specified gateway to Parbad services. - /// - /// Type of gateway. - /// Lifetime of . - IGatewayConfigurationBuilder AddGateway( - ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - where TGateway : class, IGateway; - } + /// + /// Adds the specified gateway to Parbad services. + /// + /// Type of gateway. + /// Lifetime of . + IGatewayConfigurationBuilder AddGateway( + ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + where TGateway : class, IGateway; } diff --git a/src/Parbad/src/GatewayBuilders/IGatewayConfigurationBuilder.cs b/src/Parbad/src/GatewayBuilders/IGatewayConfigurationBuilder.cs index 904f2670..b470f92d 100644 --- a/src/Parbad/src/GatewayBuilders/IGatewayConfigurationBuilder.cs +++ b/src/Parbad/src/GatewayBuilders/IGatewayConfigurationBuilder.cs @@ -6,33 +6,40 @@ using Microsoft.Extensions.DependencyInjection; using Parbad.Abstraction; -namespace Parbad.GatewayBuilders +namespace Parbad.GatewayBuilders; + +/// +/// A builder for configuring the specified gateway. +/// +/// Type of gateway. +public interface IGatewayConfigurationBuilder where TGateway : class, IGateway { /// - /// A builder for configuring the specified gateway. + /// Specifies the contract for a collection of service descriptors. + /// + IServiceCollection Services { get; } + + /// + /// Configures the accounts of type for . /// - /// Type of gateway. - public interface IGatewayConfigurationBuilder where TGateway : class, IGateway - { - /// - /// Specifies the contract for a collection of service descriptors. - /// - IServiceCollection Services { get; } + /// Account of . + /// Configures the accounts. + IGatewayConfigurationBuilder WithAccounts( + Action> configureAccounts) + where TAccount : GatewayAccount, new(); - /// - /// Configures the accounts of type for . - /// - /// Account of . - /// Configures the accounts. - IGatewayConfigurationBuilder WithAccounts( - Action> configureAccounts) - where TAccount : GatewayAccount, new(); + /// + /// Configures the required by + /// for sending HTTP requests and receiving HTTP responses. + /// + /// HttpClient configuration. + IGatewayConfigurationBuilder WithHttpClient(Action configureHttpClient); - /// - /// Configures the required by - /// for sending HTTP requests and receiving HTTP responses. - /// - /// HttpClient configuration. - IGatewayConfigurationBuilder WithHttpClient(Action configureHttpClient); - } + /// + /// Configures the HttpClient for TGateway and its implementation. + /// + IGatewayConfigurationBuilder WithHttpClient(Action configureHttpClient, + Action configureHttpClientBuilder = null) + where TGatewayApi : class + where TGatewayApiImplementation : class, TGatewayApi; } diff --git a/src/Parbad/src/Internal/GatewayBuilder.cs b/src/Parbad/src/Internal/GatewayBuilder.cs index ea8df9cd..2d2cfc0e 100644 --- a/src/Parbad/src/Internal/GatewayBuilder.cs +++ b/src/Parbad/src/Internal/GatewayBuilder.cs @@ -5,33 +5,32 @@ using Parbad.Abstraction; using Parbad.GatewayBuilders; -namespace Parbad.Internal +namespace Parbad.Internal; + +/// +internal class GatewayBuilder : IGatewayBuilder { - /// - internal class GatewayBuilder : IGatewayBuilder + /// + /// Initializes an instance of . + /// + /// + public GatewayBuilder(IServiceCollection services) { - /// - /// Initializes an instance of . - /// - /// - public GatewayBuilder(IServiceCollection services) - { - Services = services; - } + Services = services; + } - /// - public IServiceCollection Services { get; } + /// + public IServiceCollection Services { get; } - /// - public IGatewayConfigurationBuilder AddGateway( - ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - where TGateway : class, IGateway - { - Services.AddSingleton(new GatewayDescriptor(typeof(TGateway))); + /// + public IGatewayConfigurationBuilder AddGateway( + ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + where TGateway : class, IGateway + { + Services.AddSingleton(new GatewayDescriptor(typeof(TGateway))); - Services.TryAdd(serviceLifetime); + Services.TryAdd(serviceLifetime); - return new GatewayConfigurationBuilder(Services); - } + return new GatewayConfigurationBuilder(Services); } } diff --git a/src/Parbad/src/Internal/GatewayConfigurationBuilder.cs b/src/Parbad/src/Internal/GatewayConfigurationBuilder.cs index 605ca1c6..d040575d 100644 --- a/src/Parbad/src/Internal/GatewayConfigurationBuilder.cs +++ b/src/Parbad/src/Internal/GatewayConfigurationBuilder.cs @@ -2,48 +2,70 @@ // Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. using System; +using System.Net.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Parbad.Abstraction; using Parbad.GatewayBuilders; using Parbad.Net; -namespace Parbad.Internal +namespace Parbad.Internal; + +/// +internal class GatewayConfigurationBuilder : IGatewayConfigurationBuilder + where TGateway : class, IGateway { - internal class GatewayConfigurationBuilder : IGatewayConfigurationBuilder - where TGateway : class, IGateway + public GatewayConfigurationBuilder(IServiceCollection services) { - public GatewayConfigurationBuilder(IServiceCollection services) - { - Services = services; - } + Services = services; + } - public IServiceCollection Services { get; } + /// + public IServiceCollection Services { get; } - public IGatewayConfigurationBuilder WithAccounts(Action> configureAccounts) - where TAccount : GatewayAccount, new() - { - if (configureAccounts == null) throw new ArgumentNullException(nameof(configureAccounts)); + /// + public IGatewayConfigurationBuilder WithAccounts(Action> configureAccounts) + where TAccount : GatewayAccount, new() + { + if (configureAccounts == null) throw new ArgumentNullException(nameof(configureAccounts)); - configureAccounts(new GatewayAccountBuilder(Services)); + configureAccounts(new GatewayAccountBuilder(Services)); - Services - .TryAddTransient< - IGatewayAccountProvider, - GatewayAccountProvider>(); + Services + .TryAddTransient< + IGatewayAccountProvider, + GatewayAccountProvider>(); - return this; - } + return this; + } - public IGatewayConfigurationBuilder WithHttpClient(Action configureHttpClient) - { - if (configureHttpClient == null) throw new ArgumentNullException(nameof(configureHttpClient)); + /// + public IGatewayConfigurationBuilder WithHttpClient(Action configureHttpClient) + { + if (configureHttpClient == null) throw new ArgumentNullException(nameof(configureHttpClient)); + + var httpClientBuilder = Services.AddHttpClientForGateway(); + + configureHttpClient(httpClientBuilder); + + return this; + } - var httpClientBuilder = Services.AddHttpClientForGateway(); + /// + public IGatewayConfigurationBuilder WithHttpClient(Action configureHttpClient, + Action configureHttpClientBuilder = null) + where TGatewayApi : class + where TGatewayApiImplementation : class, TGatewayApi + { + if (configureHttpClient == null) throw new ArgumentNullException(nameof(configureHttpClient)); - configureHttpClient(httpClientBuilder); + var httpClientBuilder = Services.AddHttpClient(configureHttpClient); - return this; + if (configureHttpClientBuilder != null) + { + configureHttpClientBuilder(httpClientBuilder); } + + return this; } } diff --git a/src/Parbad/src/Parbad.csproj b/src/Parbad/src/Parbad.csproj index 892c3a12..8100abd6 100644 --- a/src/Parbad/src/Parbad.csproj +++ b/src/Parbad/src/Parbad.csproj @@ -3,8 +3,8 @@ Parbad Parbad - 3.9.2 - netstandard2.0;netstandard2.1;netcoreapp3.0;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + 3.10.0 + netstandard2.0;netstandard2.1;netcoreapp3.0;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0 latest Parbad Sina Soltani Parbad is a free, open-source, integrated and extensible library which connects your web applications to online payment gateways. Gateways can be added or developed by you. @@ -23,7 +23,7 @@ More information: https://github.com/Sina-Soltani/Parbad true parbad Payment Gateway virtual virtual-gateway Bank Iran Shetab IranKish Mellat Melli Sadad Parsian Pasargad Saman Asan-Pardakht پرداخت درگاه بانک ایران شتاب ایران-کیش ملت ملی سداد پارسیان پاسارگاد سامان آسان-پرداخت true - https://github.com/Sina-Soltani/Parbad/releases/tag/v3.9.2 + https://github.com/Sina-Soltani/Parbad/releases/tag/v3.10.0 @@ -47,7 +47,7 @@ More information: https://github.com/Sina-Soltani/Parbad - + diff --git a/src/Parbad/src/Parbad.xml b/src/Parbad/src/Parbad.xml index 59501fb2..4558b169 100644 --- a/src/Parbad/src/Parbad.xml +++ b/src/Parbad/src/Parbad.xml @@ -717,9 +717,34 @@ - + - Equals to ReferenceID in Parbad system. + API provided by Pasargad Bank. + + + + + Gets a token to start a payment request. + + + + + Verifies a payment. + + + + + Refunds an already paid invoice. + + + + + An Encryptor to sign data which will be sent by each request to Pasargad gateway. + + + + + Encrypts the given data using the provided Private Key. @@ -740,6 +765,13 @@ + + + Configures the HttpClient for . + + Implementation type of . + + Configures the accounts for . @@ -754,12 +786,38 @@ Configuration + + + Registers an Encryptor for . + + + + + Registers an Encryptor for . + + + + + Registers an Encryptor for . + + The invoice will be sent to Pasargad gateway. + + + Sets additional data that will be sent to Pasargad gateway when requesting a token. + + + + + + Additional Data that can be sent to Pasargad gateway when requesting a token. + + @@ -918,6 +976,21 @@ + + + + + + + + + + + + + + + @@ -1907,6 +1980,12 @@ Gets an instance of . + + + + + + diff --git a/src/Parbad/src/Storage/StorageBuilderExtensions.cs b/src/Parbad/src/Storage/StorageBuilderExtensions.cs index 7d71b656..63b49743 100644 --- a/src/Parbad/src/Storage/StorageBuilderExtensions.cs +++ b/src/Parbad/src/Storage/StorageBuilderExtensions.cs @@ -7,37 +7,37 @@ using Parbad.Storage.Abstractions; using System; -namespace Parbad.Builder +namespace Parbad.Builder; + +public static class StorageBuilderExtensions { - public static class StorageBuilderExtensions + /// + /// Configures the storage which required by Parbad for saving and loading data. + /// + /// + /// + public static IParbadBuilder ConfigureStorage(this IParbadBuilder builder, Action configureStorage) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (configureStorage == null) throw new ArgumentNullException(nameof(configureStorage)); + + var storageBuilder = new StorageBuilder(builder.Services); + storageBuilder.UseDefaultStorageManager(); + + configureStorage(storageBuilder); + + return builder; + } + + /// + /// Uses the default implementation of . + /// + /// + [Obsolete("StorageManager will be removed in a future release. The implementations are moved to the IStorage interface.")] + public static IStorageBuilder UseDefaultStorageManager(this IStorageBuilder builder) { - /// - /// Configures the storage which required by Parbad for saving and loading data. - /// - /// - /// - public static IParbadBuilder ConfigureStorage(this IParbadBuilder builder, Action configureStorage) - { - if (builder == null) throw new ArgumentNullException(nameof(builder)); - if (configureStorage == null) throw new ArgumentNullException(nameof(configureStorage)); - - var storageBuilder = new StorageBuilder(builder.Services); - storageBuilder.UseDefaultStorageManager(); - - configureStorage(storageBuilder); - - return builder; - } - - /// - /// Uses the default implementation of . - /// - /// - public static IStorageBuilder UseDefaultStorageManager(this IStorageBuilder builder) - { - if (builder == null) throw new ArgumentNullException(nameof(builder)); - - return builder.AddStorageManager(ServiceLifetime.Transient); - } + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + return builder.AddStorageManager(ServiceLifetime.Transient); } } diff --git a/src/Parbad/src/Storage/StorageManager.cs b/src/Parbad/src/Storage/StorageManager.cs index 7c3123d3..b526aefa 100644 --- a/src/Parbad/src/Storage/StorageManager.cs +++ b/src/Parbad/src/Storage/StorageManager.cs @@ -1,6 +1,7 @@ // Copyright (c) Parbad. All rights reserved. // Licensed under the GNU GENERAL PUBLIC License, Version 3.0. See License.txt in the project root for license information. +using System; using Parbad.Storage.Abstractions; using Parbad.Storage.Abstractions.Models; using System.Collections.Generic; @@ -8,71 +9,71 @@ using System.Threading; using System.Threading.Tasks; -namespace Parbad.Storage +namespace Parbad.Storage; + +/// +[Obsolete("This interface will be removed in a future release.")] +public class StorageManager : IStorageManager { - /// - public class StorageManager : IStorageManager + /// + /// Initializes an instance of . + /// + /// + [Obsolete("This interface will be removed in a future release. All methods are moved to IStorage interface.")] + public StorageManager(IStorage storage) { - /// - /// Initializes an instance of . - /// - /// - public StorageManager(IStorage storage) - { - Storage = storage; - } - - /// - /// Gets an instance of . - /// - protected IStorage Storage { get; } - - /// - public virtual Task CreatePaymentAsync(Payment payment, CancellationToken cancellationToken = default) - => Storage.CreatePaymentAsync(payment, cancellationToken); - - /// - public virtual Task UpdatePaymentAsync(Payment payment, CancellationToken cancellationToken = default) - => Storage.UpdatePaymentAsync(payment, cancellationToken); - - /// - public virtual Task CreateTransactionAsync(Transaction transaction, CancellationToken cancellationToken = default) - => Storage.CreateTransactionAsync(transaction, cancellationToken); - - /// - public virtual Task GetPaymentByTrackingNumberAsync(long trackingNumber, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); - - return Task.FromResult(Storage.Payments.SingleOrDefault(model => model.TrackingNumber == trackingNumber)); - } - - /// - public virtual Task GetPaymentByTokenAsync(string paymentToken, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); - - return Task.FromResult(Storage.Payments.SingleOrDefault(model => model.Token == paymentToken)); - } - - /// - public virtual Task DoesPaymentExistAsync(long trackingNumber, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); - - return Task.FromResult(Storage.Payments.Any(model => model.TrackingNumber == trackingNumber)); - } - - /// - public virtual Task DoesPaymentExistAsync(string paymentToken, CancellationToken cancellationToken = default) - { - return Task.FromResult(Storage.Payments.Any(model => model.Token == paymentToken)); - } - - /// - public virtual Task> GetTransactionsAsync(Payment payment, CancellationToken cancellationToken = default) - { - return Task.FromResult(Storage.Transactions.Where(model => model.PaymentId == payment.Id).ToList()); - } + Storage = storage; } + + /// + /// Gets an instance of . + /// + protected IStorage Storage { get; } + + /// + public IQueryable Payments => Storage.Payments; + + /// + public IQueryable Transactions => Storage.Transactions; + + /// + public virtual Task CreatePaymentAsync(Payment payment, CancellationToken cancellationToken = default) => + Storage.CreatePaymentAsync(payment, cancellationToken); + + /// + public virtual Task UpdatePaymentAsync(Payment payment, CancellationToken cancellationToken = default) => + Storage.UpdatePaymentAsync(payment, cancellationToken); + + public Task DeletePaymentAsync(Payment payment, CancellationToken cancellationToken = default) => + Storage.DeletePaymentAsync(payment, cancellationToken); + + /// + public virtual Task CreateTransactionAsync(Transaction transaction, CancellationToken cancellationToken = default) => + Storage.CreateTransactionAsync(transaction, cancellationToken); + + public Task UpdateTransactionAsync(Transaction transaction, CancellationToken cancellationToken = default) => + Storage.UpdateTransactionAsync(transaction, cancellationToken); + + public Task DeleteTransactionAsync(Transaction transaction, CancellationToken cancellationToken = default) => + Storage.DeleteTransactionAsync(transaction, cancellationToken); + + /// + public virtual Task GetPaymentByTrackingNumberAsync(long trackingNumber, CancellationToken cancellationToken = default) => + Storage.GetPaymentByTrackingNumberAsync(trackingNumber, cancellationToken); + + /// + public virtual Task GetPaymentByTokenAsync(string paymentToken, CancellationToken cancellationToken = default) => + Storage.GetPaymentByTokenAsync(paymentToken, cancellationToken); + + /// + public virtual Task DoesPaymentExistAsync(long trackingNumber, CancellationToken cancellationToken = default) => + Storage.DoesPaymentExistAsync(trackingNumber, cancellationToken); + + /// + public virtual Task DoesPaymentExistAsync(string paymentToken, CancellationToken cancellationToken = default) => + Storage.DoesPaymentExistAsync(paymentToken, cancellationToken); + + /// + public virtual Task> GetTransactionsAsync(Payment payment, CancellationToken cancellationToken = default) => + Storage.GetTransactionsAsync(payment, cancellationToken); } diff --git a/src/Parbad/tests/Gateway/Pasargad/PasargadGatewayTests.cs b/src/Parbad/tests/Gateway/Pasargad/PasargadGatewayTests.cs deleted file mode 100644 index b45a6903..00000000 --- a/src/Parbad/tests/Gateway/Pasargad/PasargadGatewayTests.cs +++ /dev/null @@ -1,150 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Primitives; -using Moq; -using Parbad.Builder; -using Parbad.Gateway.Pasargad; -using Parbad.Tests.Helpers; -using RichardSzalay.MockHttp; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Parbad.Tests.Gateway.Pasargad -{ - [TestClass] - public class PasargadGatewayTests - { - private IPasargadCrypto _crypto; - - private const long ExpectedTrackingNumber = 1; - private const long ExpectedAmount = 1000; - private const string ExpectedTransactionCode = "test"; - private const string ExpectedMerchantCode = "test"; - private const string ExpectedTerminalCode = "test"; - private const string ExpectedActionNumber = "1003"; - private const string ExpectedSignedValue = "test"; - - [TestInitialize] - public void Setup() - { - var mockCrypto = new Mock(); - mockCrypto - .Setup(crypto => crypto.Encrypt(It.IsAny(), It.IsAny())) - .Returns(ExpectedSignedValue); - - _crypto = mockCrypto.Object; - } - - [TestMethod] - public async Task Requesting_And_Verifying_Work() - { - const string expectedCallbackUrl = "http://www.mywebsite.com"; - const string apiCheckPaymentUrl = "http://localhost/CheckTransactionResult"; - const string apiVerificationUrl = "http://localhost/VerifyPayment"; - const string paymentPageUrl = "http://localhost/"; - - await GatewayTestHelpers.TestGatewayAsync( - gateways => - { - var builder = gateways - .AddPasargad() - .WithAccounts(accounts => - { - accounts.AddInMemory(account => - { - account.PrivateKey = "test"; - account.MerchantCode = ExpectedMerchantCode; - account.TerminalCode = ExpectedTerminalCode; - }); - }) - .WithOptions(options => - { - options.ApiCheckPaymentUrl = apiCheckPaymentUrl; - options.ApiVerificationUrl = apiVerificationUrl; - options.PaymentPageUrl = paymentPageUrl; - }); - - builder.Services.RemoveAll(); - - builder.Services.AddSingleton(_crypto); - - return builder; - }, - invoice => - { - invoice - .SetTrackingNumber(ExpectedTrackingNumber) - .SetAmount(ExpectedAmount) - .SetCallbackUrl(expectedCallbackUrl) - .UsePasargad(); - }, - handler => - { - handler - .When("*CheckTransactionResult") - .Respond(MediaTypes.Xml, GetCheckCallbackResponse()); - - handler - .When("*VerifyPayment") - .Respond(MediaTypes.Xml, GetVerificationResponse()); - }, - context => - { - context.Request.Query = new QueryCollection(new Dictionary - { - {"iN", ExpectedTrackingNumber.ToString()}, - {"iD", "test"}, - {"tref", ExpectedTransactionCode}, - {"result", "true"} - }); - }, - result => GatewayOnResultHelper.OnRequestResult( - result, - PasargadGateway.Name, - GatewayTransporterDescriptor.TransportType.Post, - expectedPaymentPageUrl: paymentPageUrl, - expectedForm: new Dictionary - { - {"merchantCode", ExpectedMerchantCode}, - {"terminalCode", ExpectedTerminalCode}, - {"invoiceNumber", ExpectedTrackingNumber.ToString()}, - {"amount", ExpectedAmount.ToString()}, - {"redirectAddress", expectedCallbackUrl}, - {"action", ExpectedActionNumber}, - {"sign", ExpectedSignedValue} - }), - result => GatewayOnResultHelper.OnFetchResult(result, ExpectedTrackingNumber, ExpectedAmount, PasargadGateway.Name), - result => GatewayOnResultHelper.OnVerifyResult(result, ExpectedTrackingNumber, ExpectedAmount, PasargadGateway.Name, expectedTransactionCode: ExpectedTransactionCode)); - } - - private static string GetCheckCallbackResponse() - { - return - "" + - "" + - "" + - $"{ExpectedTrackingNumber}" + - $"{ExpectedActionNumber}" + - $"{ExpectedMerchantCode}" + - $"{ExpectedTerminalCode}" + - "true" + - "" + - "" + - ""; - } - - private static string GetVerificationResponse() - { - return - "" + - "" + - "" + - "true" + - "" + - "" + - ""; - } - } -} diff --git a/src/Parbad/tests/Parbad.Tests.csproj b/src/Parbad/tests/Parbad.Tests.csproj index 33b14e7d..487bb9b6 100644 --- a/src/Parbad/tests/Parbad.Tests.csproj +++ b/src/Parbad/tests/Parbad.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 latest false diff --git a/tests/Parbad.Tests.Helpers/Parbad.Tests.Helpers.csproj b/tests/Parbad.Tests.Helpers/Parbad.Tests.Helpers.csproj index 17ca70c3..40079985 100644 --- a/tests/Parbad.Tests.Helpers/Parbad.Tests.Helpers.csproj +++ b/tests/Parbad.Tests.Helpers/Parbad.Tests.Helpers.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0 latest false @@ -26,7 +26,7 @@ - +