Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
KycRules.cs
1using System;
3using System.Globalization;
4using System.Linq;
5using System.Reflection.Metadata;
6using System.Text.RegularExpressions;
10
12{
16 public interface IKycRule
17 {
26 bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang = null);
27 }
28
32 public interface IAsyncKycRule: IKycRule
33 {
41 Task<(bool Ok, string? Error)> ValidateAsync(ObservableKycField field, KycProcess? process, string? lang = null);
42 }
43
47 public class RequiredRule : IKycRule
48 {
49 public bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang = null)
50 {
51 Error = string.Empty;
52 if (!Field.Required)
53 return true;
54
55 Lang ??= CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
56
57 string Label = Field.Label?.Get(Lang) ?? Field.Id;
58 bool Missing = Field.FieldType switch
59 {
60 FieldType.Date => Field is ObservableDateField DateField && DateField.DateValue is null,
61 FieldType.Picker => Field is ObservablePickerField PickerField && PickerField.SelectedOption is null,
62 FieldType.Gender => Field is ObservablePickerField GenderField && GenderField.SelectedOption is null,
63 FieldType.Radio => Field is ObservableRadioField RadioField && RadioField.SelectedOption is null,
64 FieldType.Boolean => Field is ObservableBooleanField BoolField && BoolField.BoolValue != true,
65 // Consider both UI selection list and serialized StringValue (populated during deserialization).
66 FieldType.Checkbox => Field is ObservableCheckboxField CheckboxField &&
67 (CheckboxField.SelectedOptions == null || CheckboxField.SelectedOptions.Count == 0) && string.IsNullOrEmpty(Field.StringValue),
68 FieldType.File => Field is ObservableFileField FileField && (FileField.StringValue is not string FileValue || string.IsNullOrEmpty(FileValue)),
69 FieldType.Email or FieldType.Phone or FieldType.Text => string.IsNullOrEmpty(Field.StringValue),
70 FieldType.Integer => Field is ObservableIntegerField IntField && IntField.IntValue is null,
71 FieldType.Decimal => Field is ObservableDecimalField DecField && DecField.DecimalValue is null,
72 FieldType.Country => Field is ObservableCountryField CountryField && string.IsNullOrEmpty(CountryField.CountryCode),
73 FieldType.Label or FieldType.Info => false, // info/display only, never required
74 _ => string.IsNullOrEmpty(Field.StringValue)
75 };
76
77 if (Missing)
78 Error = ServiceRef.Localizer[nameof(AppResources.IsRequired), Label];
79 return string.IsNullOrEmpty(Error);
80 }
81 }
82
86 public class LengthRule : IKycRule
87 {
88 private readonly int? min;
89 private readonly int? max;
90 private readonly string? message;
91
92 public LengthRule(int? Min, int? Max, string? Message)
93 {
94 this.min = Min;
95 this.max = Max;
96 this.message = Message;
97 }
98
99 public bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang = null)
100 {
101 Error = string.Empty;
102 string? Text = Field.StringValue;
103 if (Text is not null)
104 {
105 if (this.min.HasValue && Text.Length < this.min.Value)
106 Error = this.message ?? $"{Field.Label?.Get(Lang) ?? Field.Id} must be at least {this.min.Value} characters";
107 else if (this.max.HasValue && Text.Length > this.max.Value)
108 Error = this.message ?? $"{Field.Label?.Get(Lang) ?? Field.Id} must be at most {this.max.Value} characters";
109 }
110 return string.IsNullOrEmpty(Error);
111 }
112 }
113
117 public class RegexRule : IKycRule
118 {
119 private readonly Regex regex;
120 private readonly string? message;
121
122 public RegexRule(string Pattern, string? Message)
123 {
124 this.regex = new Regex(Pattern, RegexOptions.Compiled);
125 this.message = Message;
126 }
127
128 public bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang = null)
129 {
130 Error = string.Empty;
131 string? Text = Field.StringValue;
132 if (!string.IsNullOrEmpty(Text) && !this.regex.IsMatch(Text))
133 Error = this.message ?? $"{Field.Label?.Get(Lang) ?? Field.Id} format is invalid";
134 return string.IsNullOrEmpty(Error);
135 }
136 }
137
141 public class DateRangeRule : IKycRule
142 {
143 private readonly DateTime? min;
144 private readonly DateTime? max;
145 private readonly string? message;
146
147 public DateRangeRule(DateTime? Min, DateTime? Max, string? Message)
148 {
149 this.min = Min;
150 this.max = Max;
151 this.message = Message;
152 }
153
154 public bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang = null)
155 {
156 Error = string.Empty;
157 if (Field is ObservableDateField DateField && DateField.DateValue is DateTime Date)
158 {
159 if (this.min.HasValue && Date < this.min.Value)
160 Error = this.message ?? $"{Field.Label?.Get(Lang) ?? Field.Id} must be on or after {this.min:yyyy-MM-dd}";
161 else if (this.max.HasValue && Date > this.max.Value)
162 Error = this.message ?? $"{Field.Label?.Get(Lang) ?? Field.Id} must be on or before {this.max:yyyy-MM-dd}";
163 }
164 return string.IsNullOrEmpty(Error);
165 }
166 }
167
172 {
173 private readonly decimal? min;
174 private readonly decimal? max;
175 private readonly string? message;
176
177 public NumericRangeRule(decimal? Min, decimal? Max, string? Message)
178 {
179 this.min = Min;
180 this.max = Max;
181 this.message = Message;
182 }
183
184 public bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang = null)
185 {
186 Error = string.Empty;
187 decimal? Value = Field.FieldType switch
188 {
189 FieldType.Integer => Field is ObservableIntegerField IntField ? IntField.IntValue : null,
190 FieldType.Decimal => Field is ObservableDecimalField DecField ? DecField.DecimalValue : null,
191 _ => null
192 };
193 if (Value is decimal Dec)
194 {
195 if (this.min.HasValue && Dec < this.min.Value)
196 Error = this.message ?? $"{Field.Label?.Get(Lang) ?? Field.Id} must be at least {this.min}";
197 else if (this.max.HasValue && Dec > this.max.Value)
198 Error = this.message ?? $"{Field.Label?.Get(Lang) ?? Field.Id} must be at most {this.max}";
199 }
200 return string.IsNullOrEmpty(Error);
201 }
202 }
203
207 public class EmailRule : IKycRule
208 {
209 private static readonly Regex emailRegex = new(@"^[^@\s]+@[^@\s]+\.[^@\s]+$", RegexOptions.Compiled);
210
211 private readonly string? message;
212
213 public EmailRule(string? Message = null) => this.message = Message;
214
215 public bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang = null)
216 {
217 Error = string.Empty;
218 if (Field.FieldType == FieldType.Email && Field.StringValue is string Email && !string.IsNullOrEmpty(Email))
219 {
220 if (!emailRegex.IsMatch(Email))
221 Error = this.message ?? $"{Field.Label?.Get(Lang) ?? Field.Id} must be a valid email address";
222 }
223 return string.IsNullOrEmpty(Error);
224 }
225 }
226
230 public partial class PhoneRule : IKycRule
231 {
232 // You can use a more complex regex/library for real world
233 private static readonly Regex phoneRegex = new(@"^\+?[1-9]\d{7,15}$", RegexOptions.Compiled);
234 private readonly string? message;
235
236 public PhoneRule(string? Message = null) => this.message = Message;
237
238 public bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang = null)
239 {
240 Error = string.Empty;
241 if (Field.FieldType == FieldType.Phone && Field.StringValue is string Phone && !string.IsNullOrEmpty(Phone))
242 {
243 if (!phoneRegex.IsMatch(Phone))
244 Error = this.message ?? $"{Field.Label?.Get(Lang) ?? Field.Id} must be a valid phone number";
245 }
246 return string.IsNullOrEmpty(Error);
247 }
248 }
249
253 public partial class PersonalNumberRule(string? fieldRef, string? message) : IAsyncKycRule
254 {
255 private readonly string? fieldRef = fieldRef;
256 private readonly string? message = message;
257
258 public bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang = null)
259 {
260 (bool Ok, string? Error) Result = this.ValidateAsync(Field, process, Lang).GetAwaiter().GetResult();
261 Error = Result.Error ?? string.Empty;
262 return Result.Ok;
263 }
264
265 public async Task<(bool Ok, string? Error)> ValidateAsync(ObservableKycField Field, KycProcess? Process, string? Lang = null)
266 {
267 string Error = string.Empty;
268 string CountryCode;
269
270 if (Process is null)
271 return (true, null);
272
273 if (Field.FieldType == FieldType.Text && Field.StringValue is string Pnr && !string.IsNullOrEmpty(Pnr))
274 {
275 if (!string.IsNullOrEmpty(this.fieldRef))
276 {
277 CountryCode = Process.Values.TryGetValue(this.fieldRef, out string? Cc) && !string.IsNullOrEmpty(Cc) ? Cc : string.Empty;
278 }
279 else
280 {
281 try
282 {
283 CountryCode = ServiceRef.TagProfile.SelectedCountry ??
284 ServiceRef.TagProfile.LegalIdentity?.Properties?.FirstOrDefault(p =>p.Name.Equals(Constants.XmppProperties.Country, StringComparison.OrdinalIgnoreCase))?.Value ?? string.Empty;
285 }
286 catch (Exception)
287 {
288 CountryCode = string.Empty;
289 }
290 }
291
292 if (string.IsNullOrEmpty(CountryCode))
293 {
294 return (true, string.Empty);
295 }
296
297 NumberInformation Info = await PersonalNumberSchemes.Validate(CountryCode, Pnr);
298
299 if (Info.IsValid == false)
300 Error = this.message ?? $"{Field.Label?.Get(Lang) ?? Field.Id} is not a valid personal number";
301 }
302
303 bool Ok = string.IsNullOrEmpty(Error);
304 return (Ok, Error);
305 }
306 }
307
311 public class CountryRule : IKycRule
312 {
313 private readonly HashSet<string> validCountries;
314 private readonly string? message;
315
316 public CountryRule(IEnumerable<string> AllowedCountries, string? Message = null)
317 {
318 this.validCountries = new HashSet<string>(AllowedCountries, StringComparer.OrdinalIgnoreCase);
319 this.message = Message;
320 }
321
322 public bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang = null)
323 {
324 Error = string.Empty;
325 if (Field.FieldType == FieldType.Country && Field is ObservableCountryField CountryField && !string.IsNullOrEmpty(CountryField.CountryCode))
326 {
327 if (!this.validCountries.Contains(CountryField.CountryCode))
328 Error = this.message ?? $"{Field.Label?.Get(Lang) ?? Field.Id} is not a valid country";
329 }
330 return string.IsNullOrEmpty(Error);
331 }
332 }
333
337 public class FileRule : IKycRule
338 {
339 private readonly long? maxSizeBytes;
340 private readonly string[] allowedExtensions;
341 private readonly string? message;
342
343 public FileRule(long? MaxSizeBytes, string[] AllowedExtensions, string? Message = null)
344 {
345 this.maxSizeBytes = MaxSizeBytes;
346 this.allowedExtensions = AllowedExtensions ?? Array.Empty<string>();
347 this.message = Message;
348 }
349
350 public bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang = null)
351 {
352 Error = string.Empty;
353 return true;
354 }
355 }
356}
A set of never changing property constants and helpful values.
Definition: Constants.cs:24
A strongly-typed resource class, for looking up localized strings, etc.
static string IsRequired
Looks up a localized string similar to {0} is required.
bool? IsValid
true = valid: PersonalNumber may be normalized. false = invalid null = scheme not applicable,...
Personal Number Schemes available in different countries.
static async Task< NumberInformation > Validate(string CountryCode, string PersonalNumber)
Checks if a personal number is valid, in accordance with registered personal number schemes.
Validates if date input is within a range.
Definition: KycRules.cs:142
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Definition: KycRules.cs:154
Validates email format using a simple regex.
Definition: KycRules.cs:208
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Definition: KycRules.cs:215
Represents a parsed KYC process with pages, fields, and current values.
Definition: KycProcess.cs:11
Validates text length for string fields.
Definition: KycRules.cs:87
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Definition: KycRules.cs:99
Validates integer and decimal ranges.
Definition: KycRules.cs:172
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Definition: KycRules.cs:184
Validates phone format using a simple international regex.
Definition: KycRules.cs:231
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Definition: KycRules.cs:238
Validates string input against a regex.
Definition: KycRules.cs:118
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Definition: KycRules.cs:128
Ensures the field has a value if required.
Definition: KycRules.cs:48
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Definition: KycRules.cs:49
The base KYC field model. Use as-is for generic fields, or subclass for custom logic.
string Id
The unique identifier for the field.
abstract ? string StringValue
Gets or sets the value as a string, parsing or formatting as needed for the field type.
FieldType FieldType
The field type (input, picker, info, etc).
Base class that references services in the app.
Definition: ServiceRef.cs:43
static ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:202
static IReportingStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:370
Contract for asynchronous KYC field validation rules.
Definition: KycRules.cs:33
Task<(bool Ok, string? Error)> ValidateAsync(ObservableKycField field, KycProcess? process, string? lang=null)
Asynchronously validates a field and returns a result and optional error message.
Contract for synchronous KYC field validation rules.
Definition: KycRules.cs:17
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Definition: ImplTypes.g.cs:58
FieldType
Enumeration of all supported field types in the KYC process.