rwadurian/frontend/mobile-app/lib/features/kyc/data/kyc_service.dart

340 lines
10 KiB
Dart

import 'package:flutter/foundation.dart';
import '../../../core/network/api_client.dart';
import '../../../core/errors/exceptions.dart';
/// KYC 状态枚举
enum KycStatusType {
notStarted, // 未开始
phoneVerified, // 手机已验证
idPending, // 身份验证中
idVerified, // 身份已验证
completed, // 已完成
rejected, // 已拒绝
}
/// KYC 状态响应
class KycStatusResponse {
final bool phoneVerified;
final String kycStatus;
final String? realName;
final String? idCardNumber;
final DateTime? kycVerifiedAt;
final String? rejectedReason;
final String? phoneNumber;
KycStatusResponse({
required this.phoneVerified,
required this.kycStatus,
this.realName,
this.idCardNumber,
this.kycVerifiedAt,
this.rejectedReason,
this.phoneNumber,
});
factory KycStatusResponse.fromJson(Map<String, dynamic> json) {
return KycStatusResponse(
phoneVerified: json['phoneVerified'] as bool? ?? false,
kycStatus: json['kycStatus'] as String? ?? 'NOT_STARTED',
realName: json['realName'] as String?,
idCardNumber: json['idCardNumber'] as String?,
kycVerifiedAt: json['kycVerifiedAt'] != null
? DateTime.parse(json['kycVerifiedAt'] as String)
: null,
rejectedReason: json['rejectedReason'] as String?,
phoneNumber: json['phoneNumber'] as String?,
);
}
KycStatusType get statusType {
switch (kycStatus) {
case 'NOT_STARTED':
return KycStatusType.notStarted;
case 'PHONE_VERIFIED':
return KycStatusType.phoneVerified;
case 'ID_PENDING':
return KycStatusType.idPending;
case 'ID_VERIFIED':
return KycStatusType.idVerified;
case 'COMPLETED':
return KycStatusType.completed;
case 'REJECTED':
return KycStatusType.rejected;
default:
return KycStatusType.notStarted;
}
}
bool get isCompleted => statusType == KycStatusType.completed;
bool get needsPhoneVerification => !phoneVerified;
bool get needsIdVerification =>
statusType == KycStatusType.notStarted ||
statusType == KycStatusType.phoneVerified ||
statusType == KycStatusType.rejected;
}
/// 身份验证响应
class IdVerificationResponse {
final String requestId;
final String status;
final String? failureReason;
final String? kycStatus;
IdVerificationResponse({
required this.requestId,
required this.status,
this.failureReason,
this.kycStatus,
});
factory IdVerificationResponse.fromJson(Map<String, dynamic> json) {
return IdVerificationResponse(
requestId: json['requestId'] as String,
status: json['status'] as String,
failureReason: json['failureReason'] as String?,
kycStatus: json['kycStatus'] as String?,
);
}
bool get isSuccess => status == 'SUCCESS';
bool get isFailed => status == 'FAILED';
}
/// KYC 服务
class KycService {
static const String _tag = '[KycService]';
final ApiClient _apiClient;
KycService(this._apiClient);
/// 获取 KYC 状态
Future<KycStatusResponse> getKycStatus() async {
debugPrint('$_tag getKycStatus() - 获取 KYC 状态');
try {
final response = await _apiClient.get('/user/kyc/status');
debugPrint('$_tag getKycStatus() - 响应: ${response.statusCode}');
if (response.data == null) {
throw const ApiException('获取 KYC 状态失败: 空响应');
}
final responseData = response.data as Map<String, dynamic>;
final data = responseData['data'] as Map<String, dynamic>;
return KycStatusResponse.fromJson(data);
} on ApiException {
rethrow;
} catch (e) {
debugPrint('$_tag getKycStatus() - 异常: $e');
throw ApiException('获取 KYC 状态失败: $e');
}
}
/// 发送 KYC 手机验证码
Future<void> sendKycVerifySms() async {
debugPrint('$_tag sendKycVerifySms() - 发送验证码');
try {
final response = await _apiClient.post('/user/kyc/send-verify-sms');
debugPrint('$_tag sendKycVerifySms() - 响应: ${response.statusCode}');
} on ApiException {
rethrow;
} catch (e) {
debugPrint('$_tag sendKycVerifySms() - 异常: $e');
throw ApiException('发送验证码失败: $e');
}
}
/// 验证手机号 (KYC 流程)
Future<void> verifyPhoneForKyc(String smsCode) async {
debugPrint('$_tag verifyPhoneForKyc() - 验证手机号');
try {
final response = await _apiClient.post(
'/user/kyc/verify-phone',
data: {'smsCode': smsCode},
);
debugPrint('$_tag verifyPhoneForKyc() - 响应: ${response.statusCode}');
} on ApiException {
rethrow;
} catch (e) {
debugPrint('$_tag verifyPhoneForKyc() - 异常: $e');
throw ApiException('验证失败: $e');
}
}
/// 提交身份证验证
Future<IdVerificationResponse> submitIdVerification({
required String realName,
required String idCardNumber,
}) async {
debugPrint('$_tag submitIdVerification() - 提交身份验证');
try {
final response = await _apiClient.post(
'/user/kyc/submit-id',
data: {
'realName': realName,
'idCardNumber': idCardNumber,
},
);
debugPrint('$_tag submitIdVerification() - 响应: ${response.statusCode}');
if (response.data == null) {
throw const ApiException('提交身份验证失败: 空响应');
}
final responseData = response.data as Map<String, dynamic>;
final data = responseData['data'] as Map<String, dynamic>;
return IdVerificationResponse.fromJson(data);
} on ApiException {
rethrow;
} catch (e) {
debugPrint('$_tag submitIdVerification() - 异常: $e');
throw ApiException('提交身份验证失败: $e');
}
}
// ============ 更换手机号相关 ============
/// 获取手机号状态
Future<PhoneStatusResponse> getPhoneStatus() async {
debugPrint('$_tag getPhoneStatus() - 获取手机号状态');
try {
final response = await _apiClient.get('/user/phone-status');
debugPrint('$_tag getPhoneStatus() - 响应: ${response.statusCode}');
if (response.data == null) {
throw const ApiException('获取手机号状态失败: 空响应');
}
final responseData = response.data as Map<String, dynamic>;
final data = responseData['data'] as Map<String, dynamic>;
return PhoneStatusResponse.fromJson(data);
} on ApiException {
rethrow;
} catch (e) {
debugPrint('$_tag getPhoneStatus() - 异常: $e');
throw ApiException('获取手机号状态失败: $e');
}
}
/// 发送旧手机验证码(更换手机号第一步)
Future<void> sendOldPhoneCode() async {
debugPrint('$_tag sendOldPhoneCode() - 发送旧手机验证码');
try {
final response = await _apiClient.post('/user/change-phone/send-old-code');
debugPrint('$_tag sendOldPhoneCode() - 响应: ${response.statusCode}');
} on ApiException {
rethrow;
} catch (e) {
debugPrint('$_tag sendOldPhoneCode() - 异常: $e');
throw ApiException('发送验证码失败: $e');
}
}
/// 验证旧手机验证码(更换手机号第二步)
Future<String> verifyOldPhoneCode(String smsCode) async {
debugPrint('$_tag verifyOldPhoneCode() - 验证旧手机验证码');
try {
final response = await _apiClient.post(
'/user/change-phone/verify-old',
data: {'smsCode': smsCode},
);
debugPrint('$_tag verifyOldPhoneCode() - 响应: ${response.statusCode}');
if (response.data == null) {
throw const ApiException('验证失败: 空响应');
}
final responseData = response.data as Map<String, dynamic>;
final data = responseData['data'] as Map<String, dynamic>;
return data['changePhoneToken'] as String;
} on ApiException {
rethrow;
} catch (e) {
debugPrint('$_tag verifyOldPhoneCode() - 异常: $e');
throw ApiException('验证失败: $e');
}
}
/// 发送新手机验证码(更换手机号第三步)
Future<void> sendNewPhoneCode({
required String newPhoneNumber,
required String changePhoneToken,
}) async {
debugPrint('$_tag sendNewPhoneCode() - 发送新手机验证码');
try {
final response = await _apiClient.post(
'/user/change-phone/send-new-code',
data: {
'newPhoneNumber': newPhoneNumber,
'changePhoneToken': changePhoneToken,
},
);
debugPrint('$_tag sendNewPhoneCode() - 响应: ${response.statusCode}');
} on ApiException {
rethrow;
} catch (e) {
debugPrint('$_tag sendNewPhoneCode() - 异常: $e');
throw ApiException('发送验证码失败: $e');
}
}
/// 确认更换手机号(更换手机号第四步)
Future<void> confirmChangePhone({
required String newPhoneNumber,
required String smsCode,
required String changePhoneToken,
}) async {
debugPrint('$_tag confirmChangePhone() - 确认更换手机号');
try {
final response = await _apiClient.post(
'/user/change-phone/confirm',
data: {
'newPhoneNumber': newPhoneNumber,
'smsCode': smsCode,
'changePhoneToken': changePhoneToken,
},
);
debugPrint('$_tag confirmChangePhone() - 响应: ${response.statusCode}');
} on ApiException {
rethrow;
} catch (e) {
debugPrint('$_tag confirmChangePhone() - 异常: $e');
throw ApiException('更换手机号失败: $e');
}
}
}
/// 手机号状态响应
class PhoneStatusResponse {
final bool isBound;
final bool isVerified;
final String? phoneNumber;
final DateTime? verifiedAt;
PhoneStatusResponse({
required this.isBound,
required this.isVerified,
this.phoneNumber,
this.verifiedAt,
});
factory PhoneStatusResponse.fromJson(Map<String, dynamic> json) {
return PhoneStatusResponse(
isBound: json['isBound'] as bool? ?? false,
isVerified: json['isVerified'] as bool? ?? false,
phoneNumber: json['phoneNumber'] as String?,
verifiedAt: json['verifiedAt'] != null
? DateTime.parse(json['verifiedAt'] as String)
: null,
);
}
}