diff --git a/.gitignore b/.gitignore index dfcfd56..0372cb5 100644 --- a/.gitignore +++ b/.gitignore @@ -348,3 +348,7 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ + +# Exclude unit test Runsettings +*.runsettings +.vscode/* diff --git a/Privatbank.Business.Tests/PrivatBankAutoClient.cs b/Privatbank.Business.Tests/PrivatBankAutoClient.cs index 005cef6..38a736e 100644 --- a/Privatbank.Business.Tests/PrivatBankAutoClient.cs +++ b/Privatbank.Business.Tests/PrivatBankAutoClient.cs @@ -13,34 +13,20 @@ public class Tests public void SetUp() { _client = new PrivatbankAutoClient( - Environment.GetEnvironmentVariable("CLIENT_ID"), Environment.GetEnvironmentVariable("CLIENT_TOKEN")); } - [Test] - public async Task TestStatementsSettings() - { - var result = await _client.GetStatementsSettingsAsync(); - - Assert.IsNotEmpty(result.Phase); - Assert.IsNotEmpty(result.NonOperationalDays); - Assert.AreNotEqual(result.PreviousOperationalDay, DateTime.MinValue); - Assert.AreNotEqual(result.CurrentOperationalDay, DateTime.MinValue); - Assert.AreNotEqual(result.ServerDateTime, DateTime.MinValue); - Assert.AreNotEqual(result.DateTimeOfFinalStatement, DateTime.MinValue); - } + #region Balance [Test] - public async Task TestGetBalance() - { + public async Task TestGetBalance() { var result = await _client.GetBalanceAsync(DateTime.Now); Assert.IsNotEmpty(result); } [Test] - public async Task TestGetBalanceInterim() - { + public async Task TestGetBalanceInterim() { var result = await _client.GetBalanceInterimAsync(); Assert.IsNotEmpty(result); @@ -48,16 +34,29 @@ public async Task TestGetBalanceInterim() [Test] - public async Task TestGetBalanceFinal() - { + public async Task TestGetBalanceFinal() { var result = await _client.GetBalanceFinalAsync(); Assert.IsNotEmpty(result); + } + #endregion + + [Test] + public async Task TestStatementsSettings() + { + var result = await _client.GetStatementsSettingsAsync(); + + Assert.IsNotEmpty(result.Phase); + Assert.IsNotEmpty(result.NonOperationalDays); + Assert.AreNotEqual(result.PreviousOperationalDay, DateTime.MinValue); + Assert.AreNotEqual(result.CurrentOperationalDay, DateTime.MinValue); + Assert.AreNotEqual(result.ServerDateTime, DateTime.MinValue); + Assert.AreNotEqual(result.DateTimeOfFinalStatement, DateTime.MinValue); } + #region Transactions [Test] - public async Task TestGetTransactions() - { + public async Task TestGetTransactions() { var result = await _client.GetTransactionsAsync( DateTime.Now - TimeSpan.FromDays(30)); @@ -65,22 +64,56 @@ public async Task TestGetTransactions() } [Test] - public async Task TestGetTransactionsInterim() - { + public async Task TestGetTransactionsInterim() { var result = await _client.GetTransactionsInterimAsync(); - Assert.IsNotEmpty(result); } [Test] - public async Task TestGetTransactionsFinal() - { + public async Task TestGetTransactionsFinal() { var result = await _client.GetTransactionsFinalAsync(); Assert.NotNull(result); } + #endregion + [Test] + public async Task TestGetRecipientsAsync() { + var recipeints = await _client.GetRecipientsAsync(Data.Enums.SalaryProjects.GroupType.SALARY); + Assert.NotNull(recipeints); + } [Test] + public async Task TestGetGroups() { + var result = await _client.GetGroupsAsync(); + if (result.Count != 0) { + Assert.IsTrue(result[0].comission_rate >= 0); + Assert.IsTrue(result[0].comission_rate < 1); + } + Assert.NotNull(result); + } + + [Test] + public async Task TestGetPackets() { + var result = await _client.GetPacketsAsync(new DateTime(2022, 1, 1), new DateTime(2023, 1, 1)); + if (result.Count != 0) { + Assert.IsTrue(result[0].amount >= 0); + } + Assert.NotNull(result); + } + [Test] + public async Task TestGetPacketElements() { + var packets = await _client.GetPacketsAsync(new DateTime(2022, 1, 1), new DateTime(2023, 1, 1)); + if (packets.Count == 0) { + return; + } + var elements = await _client.GetPacketEntriesAsync(packets[0]); + if (elements.Count > 0) { + Assert.IsTrue(elements[0].amount > 0); + } + Assert.NotNull(elements); + } + + /*[Test] public async Task TestCreatePayment() { var result = await _client.CreatePaymentAsync(new Payment @@ -97,7 +130,7 @@ public async Task TestCreatePayment() Assert.NotNull(result.Reference); Assert.NotNull(result.PackedReference); - } + }*/ [TearDown] public void TearDown() diff --git a/Privatbank.Business.Tests/Privatbank.Business.Tests.csproj b/Privatbank.Business.Tests/Privatbank.Business.Tests.csproj index 20a8ffe..5110699 100644 --- a/Privatbank.Business.Tests/Privatbank.Business.Tests.csproj +++ b/Privatbank.Business.Tests/Privatbank.Business.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 false diff --git a/Privatbank.Business.Tests/Properties/launchSettings.json b/Privatbank.Business.Tests/Properties/launchSettings.json new file mode 100644 index 0000000..2e931f8 --- /dev/null +++ b/Privatbank.Business.Tests/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Privatbank.Business.Tests": { + "commandName": "Project", + "nativeDebugging": true + } + } +} \ No newline at end of file diff --git a/Privatbank.Business.Tests/example env var.runsettings.example b/Privatbank.Business.Tests/example env var.runsettings.example new file mode 100644 index 0000000..9e4c4d7 --- /dev/null +++ b/Privatbank.Business.Tests/example env var.runsettings.example @@ -0,0 +1,10 @@ + + + + + + + your_client_token + + + \ No newline at end of file diff --git a/Privatbank.Business/CHANGELOG.md b/Privatbank.Business/CHANGELOG.md new file mode 100644 index 0000000..00cbdb7 --- /dev/null +++ b/Privatbank.Business/CHANGELOG.md @@ -0,0 +1,20 @@ +# Privat.Buisness changelog +## plan for 1.0.3 + add another enum in older models like + - `Transaction.Type` + - `Transaction.DocumentType` + - `Transaction.Status` + - `Transaction.Real` + +## 1.0.2 - salary groups, api 3-0-0 + added support for client with token only + + added salary groups: + - get salary groups `GetGroupsAsync()` + - get salary groups recipients `GetRecipientsAsync(GroupType type)` + - get packets `GetPacketsAsync(DateTime from, DateTime to)` + - get packet elements `GetPacketEntriesAsync(Packet packet)` + + salary groups use enums +## 1.0.2 - transactions + functionality for transactions statements and balances \ No newline at end of file diff --git a/Privatbank.Business/Converters/StringDateTimeArrayConverter.cs b/Privatbank.Business/Converters/StringDateTimeArrayConverter.cs index bfe27f8..172eca3 100644 --- a/Privatbank.Business/Converters/StringDateTimeArrayConverter.cs +++ b/Privatbank.Business/Converters/StringDateTimeArrayConverter.cs @@ -4,12 +4,9 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Privatbank.Business.Converters -{ - internal class StringDateTimeArrayConverter : JsonConverter - { - public override DateTime[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { +namespace Privatbank.Business.Converters { + internal class StringDateTimeArrayConverter : JsonConverter { + public override DateTime[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var resultList = new List(); if (reader.TokenType == JsonTokenType.StartArray) @@ -25,8 +22,7 @@ public override DateTime[] Read(ref Utf8JsonReader reader, Type typeToConvert, J return resultList.ToArray(); } - public override void Write(Utf8JsonWriter writer, DateTime[] value, JsonSerializerOptions options) - { + public override void Write(Utf8JsonWriter writer, DateTime[] value, JsonSerializerOptions options) { throw new NotImplementedException(); } } diff --git a/Privatbank.Business/Converters/StringDateTimeConverter.cs b/Privatbank.Business/Converters/StringDateTimeConverter.cs index 4ca765b..a89e2f4 100644 --- a/Privatbank.Business/Converters/StringDateTimeConverter.cs +++ b/Privatbank.Business/Converters/StringDateTimeConverter.cs @@ -3,21 +3,18 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Privatbank.Business.Converters -{ - internal class StringDateTimeConverter : JsonConverter - { - public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { +namespace Privatbank.Business.Converters { + internal class StringDateTimeConverter : JsonConverter { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { string[] formats = { + "yyyy-MM-dd'T'HH:mm:ss.fffzzz", "dd.MM.yyyy HH:mm:ss", "dd-MM-yyyy HH:mm:ss", "dd-MM-yyyy", "dd.MM.yyyy", "HH:mm" }; - return DateTime.TryParseExact( reader.GetString(), formats, @@ -27,8 +24,7 @@ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, Jso : DateTime.MinValue; } - public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) - { + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { writer.WriteStringValue( value.ToString("dd.MM.yyyy")); } diff --git a/Privatbank.Business/Converters/StringDecimalConverter.cs b/Privatbank.Business/Converters/StringDecimalConverter.cs index 49317c9..0a3720e 100644 --- a/Privatbank.Business/Converters/StringDecimalConverter.cs +++ b/Privatbank.Business/Converters/StringDecimalConverter.cs @@ -3,17 +3,14 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Privatbank.Business.Converters -{ - internal class StringDecimalConverter : JsonConverter - { - public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return decimal.Parse(reader.GetString()); +namespace Privatbank.Business.Converters { + internal class StringDecimalConverter : JsonConverter { + static private NumberFormatInfo formatInfo = new NumberFormatInfo() { NumberDecimalSeparator = "." }; + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + return decimal.Parse(reader.GetString(), formatInfo); } - public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) - { + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) { writer.WriteStringValue( value.ToString(CultureInfo.InvariantCulture)); } diff --git a/Privatbank.Business/Converters/StringYesNoConverter.cs b/Privatbank.Business/Converters/StringYesNoConverter.cs index 3c2164b..6fa675b 100644 --- a/Privatbank.Business/Converters/StringYesNoConverter.cs +++ b/Privatbank.Business/Converters/StringYesNoConverter.cs @@ -2,20 +2,16 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Privatbank.Business.Converters -{ - internal class StringYesNoConverter : JsonConverter - { - public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { +namespace Privatbank.Business.Converters { + internal class StringYesNoConverter : JsonConverter { + public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var result = reader.GetString(); return result.Equals("yes", StringComparison.InvariantCultureIgnoreCase) || result.Equals("y", StringComparison.InvariantCultureIgnoreCase); } - public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) - { + public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) { throw new NotImplementedException(); } } diff --git a/Privatbank.Business/Data/Enums/SalaryProjects/GroupType.cs b/Privatbank.Business/Data/Enums/SalaryProjects/GroupType.cs new file mode 100644 index 0000000..d99e1c4 --- /dev/null +++ b/Privatbank.Business/Data/Enums/SalaryProjects/GroupType.cs @@ -0,0 +1,30 @@ +using System.Text.Json.Serialization; + +namespace Privatbank.Business.Data.Enums.SalaryProjects { + /// + /// group salary type + /// + [JsonConverter(typeof(JsonStringEnumConverterWithAttributeSupport))] + public enum GroupType { + /// + /// project type + /// + MASSPAYMENTS, + /// + /// other salary projects + /// + SALARY, + /// + /// project type + /// + STUDENT, + /// + /// project type + /// + HESED, + /// + /// use this ALL for request about salary group members + /// + ALL, + } +} diff --git a/Privatbank.Business/Data/Enums/SalaryProjects/Packet_Element_Status.cs b/Privatbank.Business/Data/Enums/SalaryProjects/Packet_Element_Status.cs new file mode 100644 index 0000000..675355e --- /dev/null +++ b/Privatbank.Business/Data/Enums/SalaryProjects/Packet_Element_Status.cs @@ -0,0 +1,48 @@ +using Privatbank.Business.Data.Models.SalaryProjects; +using System.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace Privatbank.Business.Data.Enums.SalaryProjects { + /// + /// salary packet element status + /// N Запис ще не перевірявся + /// N$ У записі є помилка, яку можна виправити у веб інтерфейсі Приват24 для бізнесу(наприклад 0 сума) + /// R Успішно перевірена + /// M Успішно сплачено + /// E Забракована + /// * Все останнє можна вважати помилками, на які не можливо повпливати + /// + [JsonConverter(typeof(JsonStringEnumConverterWithAttributeSupport))] + public enum Packet_Element_Status { + /// + /// N Запис ще не перевірявся + /// + [EnumMember(Value = "N")] + Not_reviewed, + /// + /// can be fixed in interface + /// + [EnumMember(Value = "N$")] + Can_be_fixed, + /// + /// successfully reviewed and accepted, awaits payment + /// + [EnumMember(Value = "R")] + Reviewed, + /// + /// paid + /// + [EnumMember(Value = "M")] + Successfully_paid, + /// + /// rejectd + /// + [EnumMember(Value = "E")] + Rejected, + /// + /// all after unfixable + /// + [EnumMember(Value = "*")] + All_bad, + } +} diff --git a/Privatbank.Business/Data/Enums/SalaryProjects/Packet_Status.cs b/Privatbank.Business/Data/Enums/SalaryProjects/Packet_Status.cs new file mode 100644 index 0000000..92ce772 --- /dev/null +++ b/Privatbank.Business/Data/Enums/SalaryProjects/Packet_Status.cs @@ -0,0 +1,82 @@ +using Privatbank.Business.Data.Models.SalaryProjects; +using System.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace Privatbank.Business.Data.Enums.SalaryProjects { + /// + /// salary project packet status, and for sub-status + /// + [JsonConverter(typeof(JsonStringEnumConverterWithAttributeSupport))] + public enum Packet_Status { + /// + /// created + /// Пакет створено, і допускає редагування + /// + [EnumMember(Value = "N")] + Created, + /// + /// reviewed + /// Пакет пройшов успішну перевірку, і його можна затвердити, або відізвати на редагування + /// + [EnumMember(Value = "W")] + Reviewd, + /// + /// confirmed - awaits singed + /// Пакет затверджено, і може бути підписаний або видалений + /// + [EnumMember(Value = "S")] + NotSinged, + /// + /// signed by accountant + /// Пакет очікує підпис директора + /// + [EnumMember(Value = "SB")] + SingedAccountant, + /// + /// singed by director + /// Пакет очікує підпис бухгалтера + /// + [EnumMember(Value = "SD")] + SingedDirector, + + /// + /// fully reviewed + /// Пакет отримав всі необхідні підписи і може бути відправлений в банк + /// + [EnumMember(Value = "S$")] + ToBeSent, + + /// + /// sent + /// Пакет відправлено в банк на обробку + /// + [EnumMember(Value = "X")] + ToBePaid, + /// + /// In review + /// Has Sub_status + /// Пакет на перевірці (N->W, N->N) + /// + [EnumMember(Value = "N")] + InRiview, + /// + /// proccessed, for more details + /// Has Sub_status + /// Пакет оброблено без помилок + /// + [EnumMember(Value = "F")] + Processed, + /// + /// blocked + /// Пакет забраковано + /// + [EnumMember(Value = "R")] + Blocked, + /// + /// deleted + /// Пакет видалено + /// + [EnumMember(Value = "D")] + Deleted + } +} diff --git a/Privatbank.Business/Data/Enums/SalaryProjects/Packet_Sub_Status.cs b/Privatbank.Business/Data/Enums/SalaryProjects/Packet_Sub_Status.cs new file mode 100644 index 0000000..ad2ca25 --- /dev/null +++ b/Privatbank.Business/Data/Enums/SalaryProjects/Packet_Sub_Status.cs @@ -0,0 +1,35 @@ +using System.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace Privatbank.Business.Data.Enums.SalaryProjects { + /// + /// sub status of a slary project pocket status, + /// + [JsonConverter(typeof(JsonStringEnumConverterWithAttributeSupport))] + public enum Packet_Sub_Status { + /// + /// packet in review + /// Пакет на перевірці (N->W, N->N) + /// + [EnumMember(Value = "V")] + InReview, + /// + /// packet processing data + /// Пакет обробляє імпорт сирих файлів (N->N) + /// + [EnumMember(Value = "I")] + Processing, + /// + /// proccessed with no issues + /// Пакет оброблено без помилок + /// + [EnumMember(Value = "F")] + Processed, + /// + /// proccessed with issues + /// Пакет оброблено з помилками + /// + [EnumMember(Value = "R")] + ProcessedWithIssue, + } +} diff --git a/Privatbank.Business/Data/Enums/SalaryProjects/SalarySystem.cs b/Privatbank.Business/Data/Enums/SalaryProjects/SalarySystem.cs new file mode 100644 index 0000000..fcd2b5d --- /dev/null +++ b/Privatbank.Business/Data/Enums/SalaryProjects/SalarySystem.cs @@ -0,0 +1,16 @@ +namespace Privatbank.Business.Data.Enums.SalaryProjects { + /// + /// salary system, depending on it changes packet elements list api endpoint + /// + public enum SalarySystem { + /// + /// MASSPAYMENTS + /// блок масових виплат, група MASSPAYMENTS + /// + reqpay, + /// + /// зарплатний блок, група SALARY/STUDENT + /// + maspay, + } +} diff --git a/Privatbank.Business/Data/Models/Balance.cs b/Privatbank.Business/Data/Models/Balance.cs index 6a58b9d..edb9e3d 100644 --- a/Privatbank.Business/Data/Models/Balance.cs +++ b/Privatbank.Business/Data/Models/Balance.cs @@ -1,14 +1,12 @@ +using Privatbank.Business.Converters; using System; using System.Text.Json.Serialization; -using Privatbank.Business.Converters; -namespace Privatbank.Business.Data.Models -{ +namespace Privatbank.Business.Data.Models { /// /// Balance record. /// - public class Balance - { + public class Balance { /// /// Account number. /// diff --git a/Privatbank.Business/Data/Models/Payment.cs b/Privatbank.Business/Data/Models/Payment.cs index f5746b3..d5e94e3 100644 --- a/Privatbank.Business/Data/Models/Payment.cs +++ b/Privatbank.Business/Data/Models/Payment.cs @@ -1,13 +1,11 @@ using System; using System.Text.Json.Serialization; -namespace Privatbank.Business.Data.Models -{ +namespace Privatbank.Business.Data.Models { /// /// Outgoing payment record. /// - public class Payment - { + public class Payment { /// /// Document number. /// diff --git a/Privatbank.Business/Data/Models/PaymentResult.cs b/Privatbank.Business/Data/Models/PaymentResult.cs index c64647b..33373e1 100644 --- a/Privatbank.Business/Data/Models/PaymentResult.cs +++ b/Privatbank.Business/Data/Models/PaymentResult.cs @@ -1,12 +1,10 @@ using System.Text.Json.Serialization; -namespace Privatbank.Business.Data.Models -{ +namespace Privatbank.Business.Data.Models { /// /// Payment result. /// - public class PaymentResult - { + public class PaymentResult { /// /// Payment reference. /// diff --git a/Privatbank.Business/Data/Models/SalaryProjects/Group.cs b/Privatbank.Business/Data/Models/SalaryProjects/Group.cs new file mode 100644 index 0000000..1183e3d --- /dev/null +++ b/Privatbank.Business/Data/Models/SalaryProjects/Group.cs @@ -0,0 +1,32 @@ +using Privatbank.Business.Data.Enums.SalaryProjects; +using System.Text.Json.Serialization; + +namespace Privatbank.Business.Data.Models.SalaryProjects { + + /// + /// payment groups, more on salary groups (7. Зарплатний проект) + /// + /// + public class Group { + /// + /// more in GroupType documentation + /// + [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonPropertyName("type")] + public GroupType type { get; set; } + + /// + /// bank comission rate for group + /// name in json - "rko" + /// + [JsonPropertyName("rko")] + public decimal comission_rate { get; set; } + + /// + /// salary group name + /// + [JsonPropertyName("name")] + public string Name { get; set; } + + } +} diff --git a/Privatbank.Business/Data/Models/SalaryProjects/PacketEntrie.cs b/Privatbank.Business/Data/Models/SalaryProjects/PacketEntrie.cs new file mode 100644 index 0000000..11244dc --- /dev/null +++ b/Privatbank.Business/Data/Models/SalaryProjects/PacketEntrie.cs @@ -0,0 +1,69 @@ +using Privatbank.Business.Data.Enums.SalaryProjects; +using System.Text.Json.Serialization; + +namespace Privatbank.Business.Data.Models.SalaryProjects { + + /// + /// a list of packet elements + /// + public class PacketEntrie { + /// + /// essentually this is an id. it is not mapped for json + /// + public string packet_reference { get; set; } + + /// + /// packet element reference - element id + /// + [JsonPropertyName("ref")] + public string reference { get; set; } + + /// + /// fio + /// + [JsonPropertyName("fio")] + public string fio { get; set; } + + /// + /// inn of person to pay to + /// + [JsonPropertyName("inn")] + public string inn { get; set; } + + /// + /// card number with mask + /// + [JsonPropertyName("cardNumber")] + public string card_number { get; set; } + + /// + /// comment + /// + [JsonPropertyName("comment")] + public string comment { get; set; } + + /// + /// amount + /// + [JsonPropertyName("amount")] + public decimal amount { get; set; } + + /// + /// status code + /// + [JsonPropertyName("status")] + public Packet_Element_Status status { get; set; } + + /// + /// err code + /// + [JsonPropertyName("errorCode")] + public string errorCode { get; set; } + + /// + /// tab num + /// + [JsonPropertyName("tabNo")] + public string tabel_num { get; set; } + } +} diff --git a/Privatbank.Business/Data/Models/SalaryProjects/Packets.cs b/Privatbank.Business/Data/Models/SalaryProjects/Packets.cs new file mode 100644 index 0000000..16d17c2 --- /dev/null +++ b/Privatbank.Business/Data/Models/SalaryProjects/Packets.cs @@ -0,0 +1,80 @@ +using Privatbank.Business.Converters; +using Privatbank.Business.Data.Enums.SalaryProjects; +using System; +using System.Text.Json.Serialization; + +namespace Privatbank.Business.Data.Models.SalaryProjects { + + /// + /// slsry projects consist of packets + /// so you have groups and packets. Packets have people to pay to + /// + public class Packet { + /// + /// essentually this is an id + /// + [JsonPropertyName("reference")] + public string reference { get; set; } + + /// + /// salary system type + /// + [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonPropertyName("system")] + public SalarySystem system { get; set; } + + /// + /// created at + /// + [JsonPropertyName("createdAt")] + [JsonConverter(typeof(StringDateTimeConverter))] + public DateTime created_at { get; set; } + + /// + /// finished at + /// + [JsonPropertyName("finishedAt")] + [JsonConverter(typeof(StringDateTimeConverter))] + public DateTime finished_at { get; set; } + + /// + /// status, + /// + [JsonPropertyName("status")] + public Packet_Status status { get; set; } + + + /// + /// sub status + /// + [JsonPropertyName("substatus")] + public Packet_Sub_Status sub_status { get; set; } + + /// + /// name + /// + [JsonPropertyName("packetName")] + public string name { get; set; } + + /// + /// count of list of person+sum + /// кількість записів + /// + [JsonPropertyName("recordCount")] + public int record_count { get; set; } + + /// + /// full packet sum + /// повна сума відомості + /// + [JsonPropertyName("recordAmount")] + public decimal amount { get; set; } + + /// + /// revision version + /// монотонно зростаючий внутрішній лічильник модифікацій + /// + [JsonPropertyName("revision")] + public int revision { get; set; } + } +} diff --git a/Privatbank.Business/Data/Models/SalaryProjects/Receiver.cs b/Privatbank.Business/Data/Models/SalaryProjects/Receiver.cs new file mode 100644 index 0000000..b32c15b --- /dev/null +++ b/Privatbank.Business/Data/Models/SalaryProjects/Receiver.cs @@ -0,0 +1,43 @@ +using Privatbank.Business.Data.Enums.SalaryProjects; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Privatbank.Business.Data.Models.SalaryProjects { + + /// + /// is a receiver in a packet, is the main object in packet element + /// + public class Receiver { + /// + /// id + /// + [JsonPropertyName("id")] + public string id { get; set; } + + /// + /// masked card number + /// + [JsonPropertyName("pan")] + public string card_masked { get; set; } + /// + /// + /// + [JsonPropertyName("fio")] + public List fio { get; set; } + /// + /// 10 digit num + /// + [JsonPropertyName("inn")] + public string inn { get; set; } + /// + /// + /// + [JsonPropertyName("group")] + public GroupType group_type { get; set; } + /// + /// tabel number + /// + [JsonPropertyName("tabn")] + public string tab_num { get; set; } + } +} diff --git a/Privatbank.Business/Data/Models/StatementsSettings.cs b/Privatbank.Business/Data/Models/StatementsSettings.cs index 5014f9d..ef3728d 100644 --- a/Privatbank.Business/Data/Models/StatementsSettings.cs +++ b/Privatbank.Business/Data/Models/StatementsSettings.cs @@ -1,14 +1,12 @@ +using Privatbank.Business.Converters; using System; using System.Text.Json.Serialization; -using Privatbank.Business.Converters; -namespace Privatbank.Business.Data.Models -{ +namespace Privatbank.Business.Data.Models { /// /// Statements settings. /// - public class StatementsSettings - { + public class StatementsSettings { /// /// Operational phase of the bank. /// diff --git a/Privatbank.Business/Data/Models/Transaction.cs b/Privatbank.Business/Data/Models/Transaction.cs index d2fbbdb..394994f 100644 --- a/Privatbank.Business/Data/Models/Transaction.cs +++ b/Privatbank.Business/Data/Models/Transaction.cs @@ -1,14 +1,12 @@ +using Privatbank.Business.Converters; using System; using System.Text.Json.Serialization; -using Privatbank.Business.Converters; -namespace Privatbank.Business.Data.Models -{ +namespace Privatbank.Business.Data.Models { /// /// Transaction. /// - public class Transaction - { + public class Transaction { /// /// Recipient code. /// diff --git a/Privatbank.Business/Data/Responses/BalancesResponse.cs b/Privatbank.Business/Data/Responses/BalancesResponse.cs index eefc493..29068f8 100644 --- a/Privatbank.Business/Data/Responses/BalancesResponse.cs +++ b/Privatbank.Business/Data/Responses/BalancesResponse.cs @@ -1,10 +1,8 @@ -using System.Text.Json.Serialization; using Privatbank.Business.Data.Models; +using System.Text.Json.Serialization; -namespace Privatbank.Business.Data.Responses -{ - internal class BalancesResponse : BasicResponse - { +namespace Privatbank.Business.Data.Responses { + internal class BalancesResponse : BasicResponse { [JsonPropertyName("balances")] public Balance[] Balances { get; set; } } diff --git a/Privatbank.Business/Data/Responses/BasicResponse.cs b/Privatbank.Business/Data/Responses/BasicResponse.cs index b70cd1a..4fda8d9 100644 --- a/Privatbank.Business/Data/Responses/BasicResponse.cs +++ b/Privatbank.Business/Data/Responses/BasicResponse.cs @@ -1,9 +1,7 @@ using System.Text.Json.Serialization; -namespace Privatbank.Business.Data.Responses -{ - internal class BasicResponse - { +namespace Privatbank.Business.Data.Responses { + internal class BasicResponse { [JsonPropertyName("exist_next_page")] public bool HasPagination { get; set; } diff --git a/Privatbank.Business/Data/Responses/GroupsResponce.cs b/Privatbank.Business/Data/Responses/GroupsResponce.cs new file mode 100644 index 0000000..ed3a625 --- /dev/null +++ b/Privatbank.Business/Data/Responses/GroupsResponce.cs @@ -0,0 +1,9 @@ +using Privatbank.Business.Data.Models.SalaryProjects; +using System.Text.Json.Serialization; + +namespace Privatbank.Business.Data.Responses { + internal class GroupsResponse : BasicResponse { + [JsonPropertyName("data")] + public Group[] Groups { get; set; } + } +} diff --git a/Privatbank.Business/Data/Responses/PacketEntriesResponse.cs b/Privatbank.Business/Data/Responses/PacketEntriesResponse.cs new file mode 100644 index 0000000..d93c0b9 --- /dev/null +++ b/Privatbank.Business/Data/Responses/PacketEntriesResponse.cs @@ -0,0 +1,9 @@ +using Privatbank.Business.Data.Models.SalaryProjects; +using System.Text.Json.Serialization; + +namespace Privatbank.Business.Data.Responses { + internal class PacketEntriesResponse : BasicResponse { + [JsonPropertyName("data")] + public PacketEntrie[] PacketEntries { get; set; } + } +} diff --git a/Privatbank.Business/Data/Responses/PacketsResponse.cs b/Privatbank.Business/Data/Responses/PacketsResponse.cs new file mode 100644 index 0000000..6c4622e --- /dev/null +++ b/Privatbank.Business/Data/Responses/PacketsResponse.cs @@ -0,0 +1,9 @@ +using Privatbank.Business.Data.Models.SalaryProjects; +using System.Text.Json.Serialization; + +namespace Privatbank.Business.Data.Responses { + internal class PacketsResponse : BasicResponse { + [JsonPropertyName("data")] + public Packet[] Packets { get; set; } + } +} diff --git a/Privatbank.Business/Data/Responses/ReceiverResponce.cs b/Privatbank.Business/Data/Responses/ReceiverResponce.cs new file mode 100644 index 0000000..effbbf4 --- /dev/null +++ b/Privatbank.Business/Data/Responses/ReceiverResponce.cs @@ -0,0 +1,9 @@ +using Privatbank.Business.Data.Models.SalaryProjects; +using System.Text.Json.Serialization; + +namespace Privatbank.Business.Data.Responses { + internal class ReceiverResponce : BasicResponse { + [JsonPropertyName("data")] + public Receiver[] Receivers { get; set; } + } +} \ No newline at end of file diff --git a/Privatbank.Business/Data/Responses/StatementsSettingsResponse.cs b/Privatbank.Business/Data/Responses/StatementsSettingsResponse.cs index afda7ce..9ea2cef 100644 --- a/Privatbank.Business/Data/Responses/StatementsSettingsResponse.cs +++ b/Privatbank.Business/Data/Responses/StatementsSettingsResponse.cs @@ -1,10 +1,8 @@ -using System.Text.Json.Serialization; using Privatbank.Business.Data.Models; +using System.Text.Json.Serialization; -namespace Privatbank.Business.Data.Responses -{ - internal class StatementsSettingsResponse : BasicResponse - { +namespace Privatbank.Business.Data.Responses { + internal class StatementsSettingsResponse : BasicResponse { [JsonPropertyName("settings")] public StatementsSettings Settings { get; set; } } diff --git a/Privatbank.Business/Data/Responses/TransactionsResponse.cs b/Privatbank.Business/Data/Responses/TransactionsResponse.cs index 95447f3..60eff22 100644 --- a/Privatbank.Business/Data/Responses/TransactionsResponse.cs +++ b/Privatbank.Business/Data/Responses/TransactionsResponse.cs @@ -1,10 +1,8 @@ -using System.Text.Json.Serialization; using Privatbank.Business.Data.Models; +using System.Text.Json.Serialization; -namespace Privatbank.Business.Data.Responses -{ - internal class TransactionsResponse : BasicResponse - { +namespace Privatbank.Business.Data.Responses { + internal class TransactionsResponse : BasicResponse { [JsonPropertyName("transactions")] public Transaction[] Transactions { get; set; } } diff --git a/Privatbank.Business/Exceptions/PrivatbankResponseException.cs b/Privatbank.Business/Exceptions/PrivatbankResponseException.cs index ff80d02..5f9fab1 100644 --- a/Privatbank.Business/Exceptions/PrivatbankResponseException.cs +++ b/Privatbank.Business/Exceptions/PrivatbankResponseException.cs @@ -1,19 +1,16 @@ using System; -namespace Privatbank.Business.Exceptions -{ +namespace Privatbank.Business.Exceptions { /// /// API response exception. /// - public class PrivatbankResponseException : Exception - { + public class PrivatbankResponseException : Exception { /// /// Create new API response exception. /// /// Error code. /// Error message. - public PrivatbankResponseException(string code, string message) : base(message) - { + public PrivatbankResponseException(string code, string message) : base(message) { Code = code; } diff --git a/Privatbank.Business/Privatbank.Business.csproj b/Privatbank.Business/Privatbank.Business.csproj index 43c27f8..d7f8e6d 100644 --- a/Privatbank.Business/Privatbank.Business.csproj +++ b/Privatbank.Business/Privatbank.Business.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.1 true Privatbank API AutoClient Client for the Privatbank AutoClient API to get balance shees, transactions and create new outgoing payments for the corporate users. @@ -11,15 +11,16 @@ https://github.com/mindcollapse/Privatbank.Business git privatbank;api;ukraine - 1.0.1 - 1.0.1 + 1.0.3 + 1.0.3 Volodymyr Smirnov Volodymyr Smirnov - 1.0.1 + 1.0.3 - + + diff --git a/Privatbank.Business/PrivatbankAutoClient.cs b/Privatbank.Business/PrivatbankAutoClient.cs index 5d08b9d..ff2335f 100644 --- a/Privatbank.Business/PrivatbankAutoClient.cs +++ b/Privatbank.Business/PrivatbankAutoClient.cs @@ -1,41 +1,53 @@ -using System; +using Privatbank.Business.Data.Models; +using Privatbank.Business.Data.Models.SalaryProjects; +using Privatbank.Business.Data.Responses; +using Privatbank.Business.Exceptions; +using System; using System.Collections.Generic; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; using System.Web; -using Privatbank.Business.Data.Models; -using Privatbank.Business.Data.Responses; -using Privatbank.Business.Exceptions; -namespace Privatbank.Business -{ +namespace Privatbank.Business { /// - /// Privatbank API AutoClient. + /// Privatbank API AutoClient. for api 3.0.0 /// - public class PrivatbankAutoClient : IDisposable - { + public class PrivatbankAutoClient : IDisposable { private const string ApiBaseAddress = "https://acp.privatbank.ua/api/"; private const int RecordsPerBatch = 20; private readonly HttpClient _httpClient; + /// + /// Initialize API AutoClient. + /// + /// Client token. + /// Mandatory parameter has not been provided. + public PrivatbankAutoClient(string clientToken) { + if (string.IsNullOrEmpty(clientToken)) + throw new ArgumentNullException(nameof(clientToken)); + + _httpClient = new HttpClient { + BaseAddress = new Uri(ApiBaseAddress) + }; + _httpClient.DefaultRequestHeaders.Add("token", clientToken); + } + /// /// Initialize API AutoClient. /// /// Client id. /// Client token. /// Mandatory parameter has not been provided. - public PrivatbankAutoClient(string clientId, string clientToken) - { + public PrivatbankAutoClient(string clientId, string clientToken) { if (string.IsNullOrEmpty(clientId)) throw new ArgumentNullException(nameof(clientId)); if (string.IsNullOrEmpty(clientToken)) throw new ArgumentNullException(nameof(clientToken)); - _httpClient = new HttpClient - { + _httpClient = new HttpClient { BaseAddress = new Uri(ApiBaseAddress) }; @@ -46,16 +58,16 @@ public PrivatbankAutoClient(string clientId, string clientToken) /// /// Dispose API client. /// - public void Dispose() - { + public void Dispose() { _httpClient.Dispose(); } - private async Task GetDataFromApi(string uri) where TResponse : BasicResponse - { - var response = await _httpClient.PostAsync(uri, - new StringContent(string.Empty, Encoding.UTF8, "application/json")); + private async Task GetDataFromApi(string uri) where TResponse : BasicResponse { + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri); + // the only normal way to set encoding for a request, it seems + request.Content = new StringContent(string.Empty, Encoding.UTF8, "application/json"); ; + var response = await _httpClient.SendAsync(request); var data = await JsonSerializer.DeserializeAsync( await response.Content.ReadAsStreamAsync()); @@ -67,15 +79,13 @@ private async Task GetDataFromApi(string uri) where TRespo private async Task GetRecordsFromApi(string uri, Func getter, string account = null, DateTime? startDate = null, - DateTime? endDate = null) where TResponse : BasicResponse - { + DateTime? endDate = null) where TResponse : BasicResponse { var result = new List(); var hasPagination = true; string nextPageId = null; - while (hasPagination) - { + while (hasPagination) { var response = await GetDataFromApi( BuildUri(uri, account, startDate, endDate, nextPageId)); @@ -88,9 +98,51 @@ private async Task GetRecordsFromApi(string uri, return result.ToArray(); } + /// + /// this method is used to deliver results with pagination through query params "page" and "page-size" + /// + /// responce with a list of TRecord + /// desired type + /// pase uri withought any params or '?' at the end + /// getter fuction that return TRecord + /// other params + /// list of desired objects + // this is ugly because there already is method for pagination? yes, but it uses other query parameters, so no. + private async Task> GetRecordsFromApiWithQParams(string uri, Func getter, Dictionary qpar = null) where TResponse : BasicResponse { + if (qpar == null) qpar = new Dictionary(); + int page = 0, page_size = 100; //page_size max 100 for api + string page_par = "page"; + string page_size_par = "page-size"; + // strictly speaking this needs to be checked if exists + qpar.Add(page_par, page.ToString()); + qpar.Add(page_size_par, page_size.ToString()); + var result = new List(); + TResponse responce; + do { + qpar[page_par] = page.ToString(); + responce = await GetDataFromApi(BuildURI(uri, qpar)); + result.AddRange(getter(responce)); + page++; + } while (getter(responce).Length == page_size); + return result; + } + + /// + /// build URI with query params past as Dictionary + /// + /// base usi + /// params with key being param name + /// + private string BuildURI(string uri, Dictionary qparams) { + string result = "?"; + foreach (var val in qparams) { + result += $"{val.Key}={val.Value}&"; + } + return uri + result; + } + private static string BuildUri(string uri, string account = null, DateTime? startDate = null, - DateTime? endDate = null, string followId = null) - { + DateTime? endDate = null, string followId = null) { var uriBuilder = new StringBuilder($"{uri}?limit={RecordsPerBatch}"); if (!string.IsNullOrEmpty(account)) @@ -114,8 +166,7 @@ private static string BuildUri(string uri, string account = null, DateTime? star /// Get statements settings. /// /// - public async Task GetStatementsSettingsAsync() - { + public async Task GetStatementsSettingsAsync() { var response = await GetDataFromApi("statements/settings"); return response.Settings; @@ -131,11 +182,10 @@ public async Task GetStatementsSettingsAsync() /// /// /// Error occured during the payment creation. - public async Task CreatePaymentAsync(Payment payment) - { + public async Task CreatePaymentAsync(Payment payment) { var response = await _httpClient.PostAsync("proxy/payment/create", new StringContent(JsonSerializer.Serialize(payment), Encoding.UTF8, "application/json")); - + if (response.IsSuccessStatusCode) return await JsonSerializer.DeserializeAsync( await response.Content.ReadAsStreamAsync()); @@ -158,8 +208,7 @@ public async Task CreatePaymentAsync(Payment payment) /// End date. /// public async Task GetBalanceAsync(DateTime startDate, string account = null, - DateTime? endDate = null) - { + DateTime? endDate = null) { return await GetRecordsFromApi( "statements/balance", r => r.Balances, account, startDate, endDate); } @@ -169,8 +218,7 @@ public async Task GetBalanceAsync(DateTime startDate, string account /// /// Account number. /// - public async Task GetBalanceInterimAsync(string account = null) - { + public async Task GetBalanceInterimAsync(string account = null) { return await GetRecordsFromApi( "statements/balance/interim", r => r.Balances, account); } @@ -180,8 +228,7 @@ public async Task GetBalanceInterimAsync(string account = null) /// /// Account number. /// - public async Task GetBalanceFinalAsync(string account = null) - { + public async Task GetBalanceFinalAsync(string account = null) { return await GetRecordsFromApi( "statements/balance/final", r => r.Balances, account); } @@ -198,8 +245,7 @@ public async Task GetBalanceFinalAsync(string account = null) /// End date. /// public async Task GetTransactionsAsync(DateTime startDate, string account = null, - DateTime? endDate = null) - { + DateTime? endDate = null) { return await GetRecordsFromApi( "statements/transactions", r => r.Transactions, account, startDate, endDate); } @@ -209,8 +255,7 @@ public async Task GetTransactionsAsync(DateTime startDate, string /// /// Account. /// - public async Task GetTransactionsInterimAsync(string account = null) - { + public async Task GetTransactionsInterimAsync(string account = null) { return await GetRecordsFromApi( "statements/transactions/interim", r => r.Transactions, account); } @@ -220,12 +265,59 @@ public async Task GetTransactionsInterimAsync(string account = nu /// /// Account. /// - public async Task GetTransactionsFinalAsync(string account = null) - { + public async Task GetTransactionsFinalAsync(string account = null) { return await GetRecordsFromApi( "statements/transactions/final", r => r.Transactions, account); } #endregion + + #region salary_groups + /// + /// Get salary groups. + /// + /// + public async Task> GetGroupsAsync() { + return await GetRecordsFromApiWithQParams( + "pay/mp/list-groups", r => r.Groups); + } + + /// + /// return Packets list, these are salary project packets + /// + /// + public async Task> GetPacketsAsync(DateTime from, DateTime to) { + Dictionary qparams = new Dictionary(); + qparams.Add("from", $"{from:yyyy-MM-dd}"); + qparams.Add("to", $"{to:yyyy-MM-dd}"); + return await GetRecordsFromApiWithQParams($"pay/apay24/packets/list", r => r.Packets, qparams); + } + + /// + /// get packet entries af a packet + /// + /// a packet returned by api peforehand, or just it ref num + /// list of all entries for a specific packet + public async Task> GetPacketEntriesAsync(Packet packet) { + // no switch because there is no public api for any other then "maspay", although I`m sure the other one is identical + // and has endpoint */reqpay/* + // so for know - no + //switch (packet.system) + + //use this method because it uses pages not pagination token + return await GetRecordsFromApiWithQParams($"pay/maspay/{packet.reference}/content", r => r.PacketEntries); + } + + /// + /// gets recipients of a salary group + /// + /// + /// + public async Task> GetRecipientsAsync(Data.Enums.SalaryProjects.GroupType type) { + Dictionary qparams = new Dictionary(); + qparams.Add("group", type.ToString()); + return await GetRecordsFromApiWithQParams("pay/mp/list-receivers", r => r.Receivers, qparams); + } + #endregion } } \ No newline at end of file diff --git a/README.md b/README.md index f56bdc7..0d2dbd5 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,11 @@ This library allows you to quickly and easily use the Privatbank AutoClient API for obtaining balance sheets, transactions and creating outgoing payments for the corporate users (legal entities and entrepreneurs). -For now, this library does not have a functionality to querying exchange rates, working with digital documents and e-statements. Feel free to send PRs or raise an issue if you need them. +For now, this library does not have a functionality to querying exchange rates, working with digital documents and e-statements. Feel free to send PRs or raise an issue if you need them. + +**see [changelog.md](./Privatbank.Business/CHANGELOG.md)** +--- ## Installation ### Prerequisites