import 'dart:typed_data'; enum IdentityStatus { unverified, pending, approved, changesRequested, denied } enum ReviewReason { passportUnreadable, selfieUnreadable, passportExpired, unableToVerify, } enum PhotoKind { passport, selfie } enum KycError { unavailable, internal, permission, format, tooLarge, unreadable, tooSmall, incomplete, sessionExpired, rateLimited, conflict, } class KycException implements Exception { const KycException(this.code); final KycError code; } class Identity { const Identity({ required this.status, this.reason, this.submissionId, this.simulated = false, this.profile = const IdentityProfile(), this.upgradePending = false, }); final IdentityStatus status; final ReviewReason? reason; final String? submissionId; final bool simulated; final IdentityProfile profile; final bool upgradePending; bool get canSubmit => status == IdentityStatus.unverified || status == IdentityStatus.changesRequested; } /// Customer assertions awaiting human review, distinct from a verified identity. class IdentityProfile { const IdentityProfile({ this.fullName = '', this.dateOfBirth = '', this.residenceCountry = '', }); final String fullName, dateOfBirth, residenceCountry; bool get isComplete { final date = DateTime.tryParse(dateOfBirth); return fullName.trim().isNotEmpty && fullName.length <= 200 && RegExp(r'^\d{4}-\d{2}-\d{2}$').hasMatch(dateOfBirth) && date != null && date.toIso8601String().startsWith(dateOfBirth) && date.isBefore(DateTime.now()) && RegExp(r'^[A-Z]{2}$').hasMatch(residenceCountry); } } /// Ephemeral image data only. Never serialize this model into storage or telemetry. class Photo { Photo(Uint8List bytes) : bytes = Uint8List.fromList(bytes).asUnmodifiableView(); final Uint8List bytes; }