3using System.Globalization;
5using System.Reflection.Metadata;
6using System.Text.RegularExpressions;
55 Lang ??= CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
57 string Label = Field.
Label?.Get(Lang) ?? Field.
Id;
58 bool Missing = Field.FieldType
switch
63 FieldType.Radio => Field is
ObservableRadioField RadioField && RadioField.SelectedOption is
null,
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)),
72 FieldType.Country => Field is
ObservableCountryField CountryField &&
string.IsNullOrEmpty(CountryField.CountryCode),
73 FieldType.Label or FieldType.Info =>
false,
79 return string.IsNullOrEmpty(Error);
88 private readonly
int? min;
89 private readonly
int? max;
90 private readonly
string? message;
92 public LengthRule(
int? Min,
int? Max,
string? Message)
96 this.message = Message;
101 Error =
string.Empty;
103 if (Text is not
null)
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";
110 return string.IsNullOrEmpty(Error);
119 private readonly Regex regex;
120 private readonly
string? message;
122 public RegexRule(
string Pattern,
string? Message)
124 this.regex =
new Regex(Pattern, RegexOptions.Compiled);
125 this.message = Message;
130 Error =
string.Empty;
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);
143 private readonly DateTime? min;
144 private readonly DateTime? max;
145 private readonly
string? message;
147 public DateRangeRule(DateTime? Min, DateTime? Max,
string? Message)
151 this.message = Message;
156 Error =
string.Empty;
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}";
164 return string.IsNullOrEmpty(Error);
173 private readonly decimal? min;
174 private readonly decimal? max;
175 private readonly
string? message;
181 this.message = Message;
186 Error =
string.Empty;
187 decimal? Value = Field.FieldType
switch
193 if (Value is decimal Dec)
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}";
200 return string.IsNullOrEmpty(Error);
209 private static readonly Regex emailRegex =
new(
@"^[^@\s]+@[^@\s]+\.[^@\s]+$", RegexOptions.Compiled);
211 private readonly
string? message;
213 public EmailRule(
string? Message =
null) => this.message = Message;
217 Error =
string.Empty;
220 if (!emailRegex.IsMatch(Email))
221 Error = this.message ?? $
"{Field.Label?.Get(Lang) ?? Field.Id} must be a valid email address";
223 return string.IsNullOrEmpty(Error);
233 private static readonly Regex phoneRegex =
new(
@"^\+?[1-9]\d{7,15}$", RegexOptions.Compiled);
234 private readonly
string? message;
236 public PhoneRule(
string? Message =
null) => this.message = Message;
240 Error =
string.Empty;
243 if (!phoneRegex.IsMatch(Phone))
244 Error = this.message ?? $
"{Field.Label?.Get(Lang) ?? Field.Id} must be a valid phone number";
246 return string.IsNullOrEmpty(Error);
253 public partial class PersonalNumberRule(string? fieldRef, string? message) : IAsyncKycRule
255 private readonly
string? fieldRef = fieldRef;
256 private readonly
string? message = message;
258 public bool Validate(
ObservableKycField Field, KycProcess? process, out
string Error,
string? Lang =
null)
260 (
bool Ok,
string? Error) Result = this.ValidateAsync(Field, process, Lang).GetAwaiter().GetResult();
261 Error = Result.Error ??
string.Empty;
265 public async Task<(
bool Ok,
string? Error)> ValidateAsync(
ObservableKycField Field, KycProcess? Process,
string? Lang =
null)
267 string Error =
string.Empty;
275 if (!
string.IsNullOrEmpty(this.fieldRef))
277 CountryCode = Process.Values.TryGetValue(this.fieldRef, out
string? Cc) && !
string.IsNullOrEmpty(Cc) ? Cc :
string.Empty;
283 CountryCode = ServiceRef.TagProfile.SelectedCountry ??
288 CountryCode =
string.Empty;
292 if (
string.IsNullOrEmpty(CountryCode))
294 return (
true,
string.Empty);
300 Error = this.message ?? $
"{Field.Label?.Get(Lang) ?? Field.Id} is not a valid personal number";
303 bool Ok =
string.IsNullOrEmpty(Error);
311 public class CountryRule : IKycRule
313 private readonly HashSet<string> validCountries;
314 private readonly
string? message;
316 public CountryRule(IEnumerable<string> AllowedCountries,
string? Message =
null)
318 this.validCountries =
new HashSet<string>(AllowedCountries, StringComparer.OrdinalIgnoreCase);
319 this.message = Message;
322 public bool Validate(
ObservableKycField Field, KycProcess? process, out
string Error,
string? Lang =
null)
324 Error =
string.Empty;
327 if (!this.validCountries.Contains(CountryField.CountryCode))
328 Error = this.message ?? $
"{Field.Label?.Get(Lang) ?? Field.Id} is not a valid country";
330 return string.IsNullOrEmpty(Error);
337 public class FileRule : IKycRule
339 private readonly
long? maxSizeBytes;
340 private readonly
string[] allowedExtensions;
341 private readonly
string? message;
343 public FileRule(
long? MaxSizeBytes,
string[] AllowedExtensions,
string? Message =
null)
345 this.maxSizeBytes = MaxSizeBytes;
346 this.allowedExtensions = AllowedExtensions ?? Array.Empty<
string>();
347 this.message = Message;
350 public bool Validate(
ObservableKycField Field, KycProcess? process, out
string Error,
string? Lang =
null)
352 Error =
string.Empty;
XMPP Protocol Properties.
const string Country
Country
A set of never changing property constants and helpful values.
A strongly-typed resource class, for looking up localized strings, etc.
static string IsRequired
Looks up a localized string similar to {0} is required.
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.
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Validates email format using a simple regex.
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Represents a parsed KYC process with pages, fields, and current values.
Validates text length for string fields.
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Validates integer and decimal ranges.
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Validates phone format using a simple international regex.
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Validates string input against a regex.
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Ensures the field has a value if required.
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
Boolean field (true/false)
Checkbox field (multi-selection)
The base KYC field model. Use as-is for generic fields, or subclass for custom logic.
string Id
The unique identifier for the field.
KycLocalizedText? Label
Field label.
abstract ? string StringValue
Gets or sets the value as a string, parsing or formatting as needed for the field type.
bool Required
Whether the field is required.
FieldType FieldType
The field type (input, picker, info, etc).
Picker field (single option selection)
Radio field (single option selection)
Base class that references services in the app.
static ITagProfile TagProfile
TAG Profile service.
static IReportingStringLocalizer Localizer
Localization service
Contract for asynchronous KYC field validation rules.
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.
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
FieldType
Enumeration of all supported field types in the KYC process.