Skip to content

Commit 7c602de

Browse files
committed
Added validation and re-fined logic for RussiaPaymentOrder
1 parent c6c511a commit 7c602de

File tree

2 files changed

+149
-49
lines changed

2 files changed

+149
-49
lines changed

QRCoder/PayloadGenerator.cs

Lines changed: 144 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System.Globalization;
55
using System.Text;
66
using System.Text.RegularExpressions;
7-
#if NETSTANDARD1_1
7+
#if NETSTANDARD1_3
88
using System.Reflection;
99
#endif
1010

@@ -2455,65 +2455,168 @@ public class RussiaPaymentOrder : Payload
24552455
private CharacterSets characterSet;
24562456
private MandatoryFields mFields;
24572457
private OptionalFields oFields;
2458+
private string separator = "|";
24582459

24592460
private RussiaPaymentOrder()
24602461
{
24612462
mFields = new MandatoryFields();
24622463
oFields = new OptionalFields();
24632464
}
2464-
2465-
public RussiaPaymentOrder(CharacterSets characterSet, string name, string personalAcc, string bankName, string BIC, string correspAcc, OptionalFields optionalFields = null) : this()
2465+
2466+
/// <summary>
2467+
/// Generates a RussiaPaymentOrder payload
2468+
/// </summary>
2469+
/// <param name="name">Name of the payee (Наименование получателя платежа)</param>
2470+
/// <param name="personalAcc">Beneficiary account number (Номер счета получателя платежа)</param>
2471+
/// <param name="bankName">Name of the beneficiary's bank (Наименование банка получателя платежа)</param>
2472+
/// <param name="BIC">BIC (БИК)</param>
2473+
/// <param name="correspAcc">Box number / account payee's bank (Номер кор./сч. банка получателя платежа)</param>
2474+
/// <param name="optionalFields">An (optional) object of additional fields</param>
2475+
/// <param name="characterSet">Type of encoding (default UTF-8)</param>
2476+
public RussiaPaymentOrder(string name, string personalAcc, string bankName, string BIC, string correspAcc, OptionalFields optionalFields = null, CharacterSets characterSet = CharacterSets.utf_8) : this()
24662477
{
24672478
this.characterSet = characterSet;
2468-
mFields.Name = validateInput(name, "Name", @"^.{1,160}$");
2469-
mFields.PersonalAcc = validateInput(personalAcc, "PersonalAcc", @"^[1-9]\d{4}[0-9ABCEHKMPTX]\d{14}$");
2470-
mFields.BankName = validateInput(bankName, "BankName", @"^.{1,45}$");
2471-
mFields.BIC = validateInput(BIC, "BIC", @"^\d{9}$");
2472-
mFields.CorrespAcc = validateInput(correspAcc, "CorrespAcc", @"^[1-9]\d{4}[0-9ABCEHKMPTX]\d{14}$");
2479+
mFields.Name = ValidateInput(name, "Name", @"^.{1,160}$");
2480+
mFields.PersonalAcc = ValidateInput(personalAcc, "PersonalAcc", @"^[1-9]\d{4}[0-9ABCEHKMPTX]\d{14}$");
2481+
mFields.BankName = ValidateInput(bankName, "BankName", @"^.{1,45}$");
2482+
mFields.BIC = ValidateInput(BIC, "BIC", @"^\d{9}$");
2483+
mFields.CorrespAcc = ValidateInput(correspAcc, "CorrespAcc", @"^[1-9]\d{4}[0-9ABCEHKMPTX]\d{14}$");
24732484

24742485
if (optionalFields != null)
24752486
oFields = optionalFields;
24762487
}
24772488

2489+
/// <summary>
2490+
/// Returns payload as string.
2491+
/// </summary>
2492+
/// <remarks>⚠ Attention: If CharacterSets was set to windows-1251 or koi8-r you should use ToBytes() instead of ToString() and pass the bytes to CreateQrCode()!</remarks>
2493+
/// <returns></returns>
24782494
public override string ToString()
24792495
{
2480-
string ret = $"ST0001" + ((int)characterSet).ToString() + $"|Name={mFields.Name}" +
2481-
$"|PersonalAcc={mFields.PersonalAcc}" +
2482-
$"|BankName={mFields.BankName}" +
2483-
$"|BIC={mFields.BIC}" +
2484-
$"|CorrespAcc={mFields.CorrespAcc}";
2496+
var cp = characterSet.ToString().Replace("_", "-");
2497+
var bytes = ToBytes();
2498+
2499+
#if !NET35 && !NET40 && !NETSTANDARD1_3_OR_GREATER
2500+
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
2501+
#endif
2502+
#if NETSTANDARD1_3
2503+
// TODO: Fix for NETSTANDARD1.1
2504+
return Encoding.GetEncoding(cp).GetString(bytes,0,bytes.Length);
2505+
#else
2506+
return Encoding.GetEncoding(cp).GetString(bytes);
2507+
#endif
2508+
}
2509+
2510+
/// <summary>
2511+
/// Returns payload as byte[].
2512+
/// </summary>
2513+
/// <remarks>Should be used if CharacterSets equals windows-1251 or koi8-r</remarks>
2514+
/// <returns></returns>
2515+
2516+
public byte[] ToBytes()
2517+
{
2518+
//Calculate the seperator
2519+
separator = DetermineSeparator();
2520+
2521+
//Create the payload string
2522+
string ret = $"ST0001" + ((int)characterSet).ToString() + //(separator != "|" ? separator : "") +
2523+
$"{separator}Name={mFields.Name}" +
2524+
$"{separator}PersonalAcc={mFields.PersonalAcc}" +
2525+
$"{separator}BankName={mFields.BankName}" +
2526+
$"{separator}BIC={mFields.BIC}" +
2527+
$"{separator}CorrespAcc={mFields.CorrespAcc}";
24852528

24862529
//Add optional fields, if filled
2487-
var optionalFieldsList = new List<string>();
2488-
#if NETSTANDARD1_1
2489-
optionalFieldsList = oFields.GetType().GetRuntimeProperties()
2530+
var optionalFieldsList = GetOptionalFieldsAsList();
2531+
if (optionalFieldsList.Count > 0)
2532+
ret += $"|{string.Join("|", optionalFieldsList.ToArray())}";
2533+
ret += separator;
2534+
2535+
//Encode return string as byte[] with correct CharacterSet
2536+
#if !NET35_OR_GREATER
2537+
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
2538+
#endif
2539+
var cp = this.characterSet.ToString().Replace("_", "-");
2540+
byte[] bytesOut = Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(cp), Encoding.UTF8.GetBytes(ret));
2541+
if (bytesOut.Length > 300)
2542+
throw new RussiaPaymentOrderException($"Data too long. Payload must not exceed 300 bytes, but actually is {bytesOut.Length} bytes long. Remove additional data fields or shorten strings/values.");
2543+
return bytesOut;
2544+
}
2545+
2546+
2547+
/// <summary>
2548+
/// Determines a valid separator
2549+
/// </summary>
2550+
/// <returns></returns>
2551+
private string DetermineSeparator()
2552+
{
2553+
// See chapter 5.2.1 of Standard (https://sbqr.ru/standard/files/standart.pdf)
2554+
2555+
var mandatoryValues = GetMandatoryFieldsAsList();
2556+
var optionalValues = GetOptionalFieldsAsList();
2557+
2558+
// Possible candidates for field separation
2559+
var separatorCandidates = new string[]{ "|", "#", ";", ":", "^", "_", "~", "{", "}", "!", "#", "$", "%", "&", "(", ")", "*", "+", ",", "/", "@" };
2560+
foreach (var sepCandidate in separatorCandidates)
2561+
{
2562+
if (!mandatoryValues.Any(x => x.Contains(sepCandidate)) && !optionalValues.Any(x => x.Contains(sepCandidate)))
2563+
return sepCandidate;
2564+
}
2565+
throw new RussiaPaymentOrderException("No valid separator found.");
2566+
}
2567+
2568+
/// <summary>
2569+
/// Takes all optional fields that are not null and returns their string represantion
2570+
/// </summary>
2571+
/// <returns>A List of strings</returns>
2572+
private List<string> GetOptionalFieldsAsList()
2573+
{
2574+
#if NETSTANDARD1_3
2575+
return oFields.GetType().GetRuntimeProperties()
24902576
.Where(field => field.GetValue(oFields) != null)
24912577
.Select(field => {
24922578
var objValue = field.GetValue(oFields, null);
2493-
var value = field.GetType().Equals(typeof(DateTime)) ? ((DateTime)objValue).ToString("dd.MM.YYYY") : objValue.ToString();
2579+
var value = field.PropertyType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString();
24942580
return $"{field.Name}={value}";
24952581
})
24962582
.ToList();
24972583
#else
2498-
optionalFieldsList = oFields.GetType().GetProperties()
2584+
return oFields.GetType().GetProperties()
24992585
.Where(field => field.GetValue(oFields, null) != null)
25002586
.Select(field => {
25012587
var objValue = field.GetValue(oFields, null);
2502-
var value = field.GetType().Equals(typeof(DateTime)) ? ((DateTime)objValue).ToString("dd.MM.YYYY") : objValue.ToString();
2588+
var value = field.PropertyType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString();
25032589
return $"{field.Name}={value}";
25042590
})
25052591
.ToList();
25062592
#endif
2507-
if (optionalFieldsList.Count > 0)
2508-
ret += $"|{string.Join("|", optionalFieldsList.ToArray())}";
2593+
}
25092594

25102595

2511-
string page = this.characterSet.ToString().Replace("_", "-");
2512-
#if NETSTANDARD1_1
2513-
var bytes = Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(page), Encoding.GetEncoding(page).GetBytes(ret));
2514-
return Encoding.GetEncoding(page).GetString(bytes,0,bytes.Length);
2596+
/// <summary>
2597+
/// Takes all mandatory fields that are not null and returns their string represantion
2598+
/// </summary>
2599+
/// <returns>A List of strings</returns>
2600+
private List<string> GetMandatoryFieldsAsList()
2601+
{
2602+
#if NETSTANDARD1_3
2603+
return mFields.GetType().GetRuntimeFields()
2604+
.Where(field => field.GetValue(mFields) != null)
2605+
.Select(field => {
2606+
var objValue = field.GetValue(mFields);
2607+
var value = field.FieldType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString();
2608+
return $"{field.Name}={value}";
2609+
})
2610+
.ToList();
25152611
#else
2516-
return Encoding.GetEncoding(page).GetString(Encoding.Convert(Encoding.Default, Encoding.GetEncoding(page), Encoding.GetEncoding(page).GetBytes(ret)));
2612+
return mFields.GetType().GetFields()
2613+
.Where(field => field.GetValue(mFields) != null)
2614+
.Select(field => {
2615+
var objValue = field.GetValue(mFields);
2616+
var value = field.FieldType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString();
2617+
return $"{field.Name}={value}";
2618+
})
2619+
.ToList();
25172620
#endif
25182621
}
25192622

@@ -2525,9 +2628,9 @@ public override string ToString()
25252628
/// <param name="pattern">A regex pattern to be used for validation</param>
25262629
/// <param name="errorText">An optional error text. If null, a standard error text is generated</param>
25272630
/// <returns>Input value (in case it is valid)</returns>
2528-
private static string validateInput(string input, string fieldname, string pattern, string errorText = null)
2631+
private static string ValidateInput(string input, string fieldname, string pattern, string errorText = null)
25292632
{
2530-
return validateInput(input, fieldname, new string[] { pattern }, errorText);
2633+
return ValidateInput(input, fieldname, new string[] { pattern }, errorText);
25312634
}
25322635

25332636
/// <summary>
@@ -2538,7 +2641,7 @@ private static string validateInput(string input, string fieldname, string patte
25382641
/// <param name="patterns">An array of regex patterns to be used for validation</param>
25392642
/// <param name="errorText">An optional error text. If null, a standard error text is generated</param>
25402643
/// <returns>Input value (in case it is valid)</returns>
2541-
private static string validateInput(string input, string fieldname, string[] patterns, string errorText = null)
2644+
private static string ValidateInput(string input, string fieldname, string[] patterns, string errorText = null)
25422645
{
25432646
if (input == null)
25442647
throw new RussiaPaymentOrderException($"The input for '{fieldname}' must not be null.");
@@ -2569,7 +2672,7 @@ public class OptionalFields
25692672
public string Sum
25702673
{
25712674
get { return _sum; }
2572-
set { _sum = validateInput(value, "Sum", @"^\d{1,18}$"); }
2675+
set { _sum = ValidateInput(value, "Sum", @"^\d{1,18}$"); }
25732676
}
25742677

25752678
private string _purpose;
@@ -2580,7 +2683,7 @@ public string Sum
25802683
public string Purpose
25812684
{
25822685
get { return _purpose; }
2583-
set { _purpose = validateInput(value, "Purpose", @"^.{1,160}$"); }
2686+
set { _purpose = ValidateInput(value, "Purpose", @"^.{1,160}$"); }
25842687
}
25852688

25862689
private string _payeeInn;
@@ -2591,7 +2694,7 @@ public string Purpose
25912694
public string PayeeINN
25922695
{
25932696
get { return _payeeInn; }
2594-
set { _payeeInn = validateInput(value, "PayeeINN", @"^.{1,12}$"); }
2697+
set { _payeeInn = ValidateInput(value, "PayeeINN", @"^.{1,12}$"); }
25952698
}
25962699

25972700
private string _payerInn;
@@ -2602,7 +2705,7 @@ public string PayeeINN
26022705
public string PayerINN
26032706
{
26042707
get { return _payerInn; }
2605-
set { _payerInn = validateInput(value, "PayerINN", @"^.{1,12}$"); }
2708+
set { _payerInn = ValidateInput(value, "PayerINN", @"^.{1,12}$"); }
26062709
}
26072710

26082711
private string _drawerStatus;
@@ -2613,7 +2716,7 @@ public string PayerINN
26132716
public string DrawerStatus
26142717
{
26152718
get { return _drawerStatus; }
2616-
set { _drawerStatus = validateInput(value, "DrawerStatus", @"^.{1,2}$"); }
2719+
set { _drawerStatus = ValidateInput(value, "DrawerStatus", @"^.{1,2}$"); }
26172720
}
26182721

26192722
private string _kpp;
@@ -2624,7 +2727,7 @@ public string DrawerStatus
26242727
public string KPP
26252728
{
26262729
get { return _kpp; }
2627-
set { _kpp = validateInput(value, "KPP", @"^.{1,9}$"); }
2730+
set { _kpp = ValidateInput(value, "KPP", @"^.{1,9}$"); }
26282731
}
26292732

26302733
private string _cbc;
@@ -2635,7 +2738,7 @@ public string KPP
26352738
public string CBC
26362739
{
26372740
get { return _cbc; }
2638-
set { _cbc = validateInput(value, "CBC", @"^.{1,20}$"); }
2741+
set { _cbc = ValidateInput(value, "CBC", @"^.{1,20}$"); }
26392742
}
26402743

26412744
private string _oktmo;
@@ -2646,7 +2749,7 @@ public string CBC
26462749
public string OKTMO
26472750
{
26482751
get { return _oktmo; }
2649-
set { _oktmo = validateInput(value, "OKTMO", @"^.{1,11}$"); }
2752+
set { _oktmo = ValidateInput(value, "OKTMO", @"^.{1,11}$"); }
26502753
}
26512754

26522755
private string _paytReason;
@@ -2657,7 +2760,7 @@ public string OKTMO
26572760
public string PaytReason
26582761
{
26592762
get { return _paytReason; }
2660-
set { _paytReason = validateInput(value, "PaytReason", @"^.{1,2}$"); }
2763+
set { _paytReason = ValidateInput(value, "PaytReason", @"^.{1,2}$"); }
26612764
}
26622765

26632766
private string _taxPeriod;
@@ -2668,7 +2771,7 @@ public string PaytReason
26682771
public string TaxPeriod
26692772
{
26702773
get { return _taxPeriod; }
2671-
set { _taxPeriod = validateInput(value, "ТaxPeriod", @"^.{1,10}$"); }
2774+
set { _taxPeriod = ValidateInput(value, "ТaxPeriod", @"^.{1,10}$"); }
26722775
}
26732776

26742777
private string _docNo;
@@ -2679,7 +2782,7 @@ public string TaxPeriod
26792782
public string DocNo
26802783
{
26812784
get { return _docNo; }
2682-
set { _docNo = validateInput(value, "DocNo", @"^.{1,15}$"); }
2785+
set { _docNo = ValidateInput(value, "DocNo", @"^.{1,15}$"); }
26832786
}
26842787

26852788
/// <summary>
@@ -2696,7 +2799,7 @@ public string DocNo
26962799
public string TaxPaytKind
26972800
{
26982801
get { return _taxPaytKind; }
2699-
set { _taxPaytKind = validateInput(value, "TaxPaytKind", @"^.{1,2}$"); }
2802+
set { _taxPaytKind = ValidateInput(value, "TaxPaytKind", @"^.{1,2}$"); }
27002803
}
27012804

27022805
/**************************************************************************
@@ -2931,17 +3034,10 @@ public enum CharacterSets
29313034

29323035
public class RussiaPaymentOrderException : Exception
29333036
{
2934-
public RussiaPaymentOrderException()
2935-
{
2936-
}
29373037
public RussiaPaymentOrderException(string message)
29383038
: base(message)
29393039
{
29403040
}
2941-
public RussiaPaymentOrderException(string message, Exception inner)
2942-
: base(message, inner)
2943-
{
2944-
}
29453041
}
29463042

29473043
}

QRCoder/QRCoder.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net35;net40;netstandard1.1;netstandard2.0;net5.0;net5.0-windows</TargetFrameworks>
4+
<TargetFrameworks>net35;net40;netstandard1.3;netstandard2.0;net5.0;net5.0-windows</TargetFrameworks>
55
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
66
<UseWindowsForms Condition="'$(TargetFramework)' == 'net5.0-windows'">true</UseWindowsForms>
77
<UseWPF Condition="'$(TargetFramework)' == 'net5.0-windows'">true</UseWPF>
@@ -42,6 +42,10 @@
4242
<Reference Include="WindowsBase" />
4343
</ItemGroup>
4444

45+
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' or '$(TargetFramework)' == 'netstandard2.0' ">
46+
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
47+
</ItemGroup>
48+
4549
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'net5.0' ">
4650
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
4751
</ItemGroup>

0 commit comments

Comments
 (0)