758 lines
22 KiB
Dart
758 lines
22 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import '../../../core/network/api_client.dart';
|
|
import '../../../core/errors/exceptions.dart';
|
|
|
|
/// KYC 状态枚举 (综合状态)
|
|
enum KycStatusType {
|
|
notStarted, // 未开始
|
|
realNameVerified, // 层级1完成: 实名认证通过
|
|
faceVerified, // 层级2完成: 实人认证通过
|
|
kycVerified, // 层级3完成: KYC认证通过
|
|
completed, // 所有层级完成
|
|
rejected, // 被拒绝
|
|
}
|
|
|
|
/// KYC 配置响应
|
|
class KycConfigResponse {
|
|
final bool level1Enabled; // 实名认证开关
|
|
final bool level2Enabled; // 实人认证开关
|
|
final bool level3Enabled; // KYC证件照开关
|
|
|
|
KycConfigResponse({
|
|
required this.level1Enabled,
|
|
required this.level2Enabled,
|
|
required this.level3Enabled,
|
|
});
|
|
|
|
factory KycConfigResponse.fromJson(Map<String, dynamic> json) {
|
|
return KycConfigResponse(
|
|
level1Enabled: json['level1Enabled'] as bool? ?? true,
|
|
level2Enabled: json['level2Enabled'] as bool? ?? true,
|
|
level3Enabled: json['level3Enabled'] as bool? ?? true,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 层级1状态
|
|
class KycLevel1Status {
|
|
final bool enabled;
|
|
final bool verified;
|
|
final DateTime? verifiedAt;
|
|
final String? realName;
|
|
final String? idCardNumber;
|
|
|
|
KycLevel1Status({
|
|
required this.enabled,
|
|
required this.verified,
|
|
this.verifiedAt,
|
|
this.realName,
|
|
this.idCardNumber,
|
|
});
|
|
|
|
factory KycLevel1Status.fromJson(Map<String, dynamic> json) {
|
|
return KycLevel1Status(
|
|
enabled: json['enabled'] as bool? ?? true,
|
|
verified: json['verified'] as bool? ?? false,
|
|
verifiedAt: json['verifiedAt'] != null
|
|
? DateTime.parse(json['verifiedAt'] as String)
|
|
: null,
|
|
realName: json['realName'] as String?,
|
|
idCardNumber: json['idCardNumber'] as String?,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 层级2状态
|
|
class KycLevel2Status {
|
|
final bool enabled;
|
|
final bool verified;
|
|
final DateTime? verifiedAt;
|
|
final bool canStart;
|
|
|
|
KycLevel2Status({
|
|
required this.enabled,
|
|
required this.verified,
|
|
this.verifiedAt,
|
|
required this.canStart,
|
|
});
|
|
|
|
factory KycLevel2Status.fromJson(Map<String, dynamic> json) {
|
|
return KycLevel2Status(
|
|
enabled: json['enabled'] as bool? ?? true,
|
|
verified: json['verified'] as bool? ?? false,
|
|
verifiedAt: json['verifiedAt'] != null
|
|
? DateTime.parse(json['verifiedAt'] as String)
|
|
: null,
|
|
canStart: json['canStart'] as bool? ?? false,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 层级3状态
|
|
class KycLevel3Status {
|
|
final bool enabled;
|
|
final bool verified;
|
|
final DateTime? verifiedAt;
|
|
final bool hasIdCardFront;
|
|
final bool hasIdCardBack;
|
|
final bool canStart;
|
|
|
|
KycLevel3Status({
|
|
required this.enabled,
|
|
required this.verified,
|
|
this.verifiedAt,
|
|
required this.hasIdCardFront,
|
|
required this.hasIdCardBack,
|
|
required this.canStart,
|
|
});
|
|
|
|
factory KycLevel3Status.fromJson(Map<String, dynamic> json) {
|
|
return KycLevel3Status(
|
|
enabled: json['enabled'] as bool? ?? true,
|
|
verified: json['verified'] as bool? ?? false,
|
|
verifiedAt: json['verifiedAt'] != null
|
|
? DateTime.parse(json['verifiedAt'] as String)
|
|
: null,
|
|
hasIdCardFront: json['hasIdCardFront'] as bool? ?? false,
|
|
hasIdCardBack: json['hasIdCardBack'] as bool? ?? false,
|
|
canStart: json['canStart'] as bool? ?? false,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// KYC 完整状态响应 (支持三层认证)
|
|
class KycStatusResponse {
|
|
final KycConfigResponse config;
|
|
final List<String> requiredSteps;
|
|
|
|
// 层级1: 实名认证
|
|
final KycLevel1Status level1;
|
|
|
|
// 层级2: 实人认证
|
|
final KycLevel2Status level2;
|
|
|
|
// 层级3: KYC
|
|
final KycLevel3Status level3;
|
|
|
|
// 综合状态
|
|
final String kycStatus;
|
|
final bool isCompleted;
|
|
final String? rejectedReason;
|
|
final String? phoneNumber;
|
|
final bool phoneVerified;
|
|
|
|
KycStatusResponse({
|
|
required this.config,
|
|
required this.requiredSteps,
|
|
required this.level1,
|
|
required this.level2,
|
|
required this.level3,
|
|
required this.kycStatus,
|
|
required this.isCompleted,
|
|
this.rejectedReason,
|
|
this.phoneNumber,
|
|
required this.phoneVerified,
|
|
});
|
|
|
|
factory KycStatusResponse.fromJson(Map<String, dynamic> json) {
|
|
return KycStatusResponse(
|
|
config: KycConfigResponse.fromJson(
|
|
json['config'] as Map<String, dynamic>? ?? {},
|
|
),
|
|
requiredSteps: (json['requiredSteps'] as List?)?.cast<String>() ?? [],
|
|
level1: KycLevel1Status.fromJson(
|
|
json['level1'] as Map<String, dynamic>? ?? {},
|
|
),
|
|
level2: KycLevel2Status.fromJson(
|
|
json['level2'] as Map<String, dynamic>? ?? {},
|
|
),
|
|
level3: KycLevel3Status.fromJson(
|
|
json['level3'] as Map<String, dynamic>? ?? {},
|
|
),
|
|
kycStatus: json['kycStatus'] as String? ?? 'NOT_STARTED',
|
|
isCompleted: json['isCompleted'] as bool? ?? false,
|
|
rejectedReason: json['rejectedReason'] as String?,
|
|
phoneNumber: json['phoneNumber'] as String?,
|
|
phoneVerified: json['phoneVerified'] as bool? ?? false,
|
|
);
|
|
}
|
|
|
|
KycStatusType get statusType {
|
|
switch (kycStatus) {
|
|
case 'REAL_NAME_VERIFIED':
|
|
return KycStatusType.realNameVerified;
|
|
case 'FACE_VERIFIED':
|
|
return KycStatusType.faceVerified;
|
|
case 'KYC_VERIFIED':
|
|
return KycStatusType.kycVerified;
|
|
case 'COMPLETED':
|
|
return KycStatusType.completed;
|
|
case 'REJECTED':
|
|
return KycStatusType.rejected;
|
|
default:
|
|
return KycStatusType.notStarted;
|
|
}
|
|
}
|
|
|
|
/// 兼容旧代码
|
|
String? get realName => level1.realName;
|
|
String? get idCardNumber => level1.idCardNumber;
|
|
DateTime? get kycVerifiedAt => level3.verifiedAt ?? level2.verifiedAt ?? level1.verifiedAt;
|
|
bool get needsIdVerification => !level1.verified && level1.enabled;
|
|
}
|
|
|
|
/// 实名认证响应
|
|
class RealNameVerifyResponse {
|
|
final bool success;
|
|
final int level;
|
|
final String? status;
|
|
final String? message;
|
|
final String? errorMessage;
|
|
|
|
RealNameVerifyResponse({
|
|
required this.success,
|
|
required this.level,
|
|
this.status,
|
|
this.message,
|
|
this.errorMessage,
|
|
});
|
|
|
|
factory RealNameVerifyResponse.fromJson(Map<String, dynamic> json) {
|
|
return RealNameVerifyResponse(
|
|
success: json['success'] as bool? ?? false,
|
|
level: json['level'] as int? ?? 1,
|
|
status: json['status'] as String?,
|
|
message: json['message'] as String?,
|
|
errorMessage: json['errorMessage'] as String?,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 人脸认证初始化响应
|
|
class FaceVerifyInitResponse {
|
|
final bool success;
|
|
final String? certifyId;
|
|
final String? certifyUrl;
|
|
|
|
FaceVerifyInitResponse({
|
|
required this.success,
|
|
this.certifyId,
|
|
this.certifyUrl,
|
|
});
|
|
|
|
factory FaceVerifyInitResponse.fromJson(Map<String, dynamic> json) {
|
|
return FaceVerifyInitResponse(
|
|
success: json['success'] as bool? ?? false,
|
|
certifyId: json['certifyId'] as String?,
|
|
certifyUrl: json['certifyUrl'] as String?,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 人脸认证查询响应
|
|
class FaceVerifyQueryResponse {
|
|
final bool success;
|
|
final bool passed;
|
|
final String status;
|
|
final String? errorMessage;
|
|
|
|
FaceVerifyQueryResponse({
|
|
required this.success,
|
|
required this.passed,
|
|
required this.status,
|
|
this.errorMessage,
|
|
});
|
|
|
|
factory FaceVerifyQueryResponse.fromJson(Map<String, dynamic> json) {
|
|
return FaceVerifyQueryResponse(
|
|
success: json['success'] as bool? ?? false,
|
|
passed: json['passed'] as bool? ?? false,
|
|
status: json['status'] as String? ?? 'PENDING',
|
|
errorMessage: json['errorMessage'] as String?,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 证件照上传响应
|
|
class IdCardUploadResponse {
|
|
final bool success;
|
|
final String side;
|
|
final String? imageUrl;
|
|
final IdCardOcrResult? ocrResult;
|
|
|
|
IdCardUploadResponse({
|
|
required this.success,
|
|
required this.side,
|
|
this.imageUrl,
|
|
this.ocrResult,
|
|
});
|
|
|
|
factory IdCardUploadResponse.fromJson(Map<String, dynamic> json) {
|
|
return IdCardUploadResponse(
|
|
success: json['success'] as bool? ?? false,
|
|
side: json['side'] as String? ?? '',
|
|
imageUrl: json['imageUrl'] as String?,
|
|
ocrResult: json['ocrResult'] != null
|
|
? IdCardOcrResult.fromJson(json['ocrResult'] as Map<String, dynamic>)
|
|
: null,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// OCR 识别结果
|
|
class IdCardOcrResult {
|
|
final String? name;
|
|
final String? idNumber;
|
|
final String? address;
|
|
final String? issueAuthority;
|
|
final String? validPeriod;
|
|
|
|
IdCardOcrResult({
|
|
this.name,
|
|
this.idNumber,
|
|
this.address,
|
|
this.issueAuthority,
|
|
this.validPeriod,
|
|
});
|
|
|
|
factory IdCardOcrResult.fromJson(Map<String, dynamic> json) {
|
|
return IdCardOcrResult(
|
|
name: json['name'] as String?,
|
|
idNumber: json['idNumber'] as String?,
|
|
address: json['address'] as String?,
|
|
issueAuthority: json['issueAuthority'] as String?,
|
|
validPeriod: json['validPeriod'] as String?,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// KYC 确认响应
|
|
class KycConfirmResponse {
|
|
final bool success;
|
|
final int level;
|
|
final String status;
|
|
final String message;
|
|
|
|
KycConfirmResponse({
|
|
required this.success,
|
|
required this.level,
|
|
required this.status,
|
|
required this.message,
|
|
});
|
|
|
|
factory KycConfirmResponse.fromJson(Map<String, dynamic> json) {
|
|
return KycConfirmResponse(
|
|
success: json['success'] as bool? ?? false,
|
|
level: json['level'] as int? ?? 3,
|
|
status: json['status'] as String? ?? '',
|
|
message: json['message'] as String? ?? '',
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 手机号状态响应
|
|
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,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 验证旧手机响应
|
|
class VerifyOldPhoneResponse {
|
|
final String changePhoneToken;
|
|
final bool wasAlreadyVerified;
|
|
|
|
VerifyOldPhoneResponse({
|
|
required this.changePhoneToken,
|
|
required this.wasAlreadyVerified,
|
|
});
|
|
|
|
factory VerifyOldPhoneResponse.fromJson(Map<String, dynamic> json) {
|
|
return VerifyOldPhoneResponse(
|
|
changePhoneToken: json['changePhoneToken'] as String,
|
|
wasAlreadyVerified: json['wasAlreadyVerified'] as bool? ?? true,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// KYC 服务 - 支持三层认证
|
|
/// 层级1: 实名认证 (二要素: 姓名+身份证号)
|
|
/// 层级2: 实人认证 (人脸活体检测)
|
|
/// 层级3: KYC (证件照上传验证)
|
|
class KycService {
|
|
static const String _tag = '[KycService]';
|
|
final ApiClient _apiClient;
|
|
|
|
KycService(this._apiClient);
|
|
|
|
// ============ 获取状态和配置 ============
|
|
|
|
/// 获取 KYC 配置(三层认证开关)
|
|
Future<KycConfigResponse> getKycConfig() async {
|
|
debugPrint('$_tag getKycConfig() - 获取 KYC 配置');
|
|
|
|
try {
|
|
final response = await _apiClient.get('/user/kyc/config');
|
|
debugPrint('$_tag getKycConfig() - 响应: ${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 KycConfigResponse.fromJson(data);
|
|
} on ApiException {
|
|
rethrow;
|
|
} catch (e) {
|
|
debugPrint('$_tag getKycConfig() - 异常: $e');
|
|
throw ApiException('获取 KYC 配置失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 获取 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>;
|
|
// 后端返回格式: { success, data: { code, message, data: {...实际数据...} } }
|
|
final outerData = responseData['data'] as Map<String, dynamic>;
|
|
final data = outerData['data'] as Map<String, dynamic>;
|
|
return KycStatusResponse.fromJson(data);
|
|
} on ApiException {
|
|
rethrow;
|
|
} catch (e) {
|
|
debugPrint('$_tag getKycStatus() - 异常: $e');
|
|
throw ApiException('获取 KYC 状态失败: $e');
|
|
}
|
|
}
|
|
|
|
// ============ 层级1: 实名认证 ============
|
|
|
|
/// 提交实名认证(二要素验证)
|
|
Future<RealNameVerifyResponse> submitRealNameVerification({
|
|
required String realName,
|
|
required String idCardNumber,
|
|
}) async {
|
|
debugPrint('$_tag submitRealNameVerification() - 提交实名认证');
|
|
|
|
try {
|
|
final response = await _apiClient.post(
|
|
'/user/kyc/level1/submit',
|
|
data: {
|
|
'realName': realName,
|
|
'idCardNumber': idCardNumber,
|
|
},
|
|
);
|
|
debugPrint('$_tag submitRealNameVerification() - 响应: ${response.statusCode}');
|
|
|
|
if (response.data == null) {
|
|
throw const ApiException('提交失败: 空响应');
|
|
}
|
|
|
|
final responseData = response.data as Map<String, dynamic>;
|
|
// 后端返回格式: { success, data: { code, message, data: {...实际数据...} } }
|
|
final outerData = responseData['data'] as Map<String, dynamic>;
|
|
final data = outerData['data'] as Map<String, dynamic>;
|
|
return RealNameVerifyResponse.fromJson(data);
|
|
} on ApiException {
|
|
rethrow;
|
|
} catch (e) {
|
|
debugPrint('$_tag submitRealNameVerification() - 异常: $e');
|
|
throw ApiException('实名认证失败: $e');
|
|
}
|
|
}
|
|
|
|
// ============ 层级2: 实人认证 ============
|
|
|
|
/// 初始化人脸活体检测
|
|
Future<FaceVerifyInitResponse> initFaceVerification({String? metaInfo}) async {
|
|
debugPrint('$_tag initFaceVerification() - 初始化人脸认证');
|
|
|
|
try {
|
|
final response = await _apiClient.post(
|
|
'/user/kyc/level2/init',
|
|
data: {
|
|
if (metaInfo != null) 'metaInfo': metaInfo,
|
|
},
|
|
);
|
|
debugPrint('$_tag initFaceVerification() - 响应: ${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 FaceVerifyInitResponse.fromJson(data);
|
|
} on ApiException {
|
|
rethrow;
|
|
} catch (e) {
|
|
debugPrint('$_tag initFaceVerification() - 异常: $e');
|
|
throw ApiException('初始化人脸认证失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 查询人脸认证结果
|
|
Future<FaceVerifyQueryResponse> queryFaceVerification(String certifyId) async {
|
|
debugPrint('$_tag queryFaceVerification() - 查询人脸认证结果');
|
|
|
|
try {
|
|
final response = await _apiClient.get(
|
|
'/user/kyc/level2/query',
|
|
queryParameters: {'certifyId': certifyId},
|
|
);
|
|
debugPrint('$_tag queryFaceVerification() - 响应: ${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 FaceVerifyQueryResponse.fromJson(data);
|
|
} on ApiException {
|
|
rethrow;
|
|
} catch (e) {
|
|
debugPrint('$_tag queryFaceVerification() - 异常: $e');
|
|
throw ApiException('查询人脸认证结果失败: $e');
|
|
}
|
|
}
|
|
|
|
// ============ 层级3: KYC 证件照 ============
|
|
|
|
/// 上传身份证照片
|
|
Future<IdCardUploadResponse> uploadIdCardPhoto({
|
|
required String side, // 'front' 或 'back'
|
|
required List<int> imageBytes,
|
|
required String fileName,
|
|
}) async {
|
|
debugPrint('$_tag uploadIdCardPhoto() - 上传证件照 side=$side');
|
|
|
|
try {
|
|
final response = await _apiClient.uploadFile(
|
|
'/user/kyc/level3/upload/$side',
|
|
imageBytes,
|
|
fileName,
|
|
fieldName: 'file',
|
|
);
|
|
debugPrint('$_tag uploadIdCardPhoto() - 响应: ${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 IdCardUploadResponse.fromJson(data);
|
|
} on ApiException {
|
|
rethrow;
|
|
} catch (e) {
|
|
debugPrint('$_tag uploadIdCardPhoto() - 异常: $e');
|
|
throw ApiException('上传证件照失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 确认提交 KYC
|
|
Future<KycConfirmResponse> confirmKycSubmission() async {
|
|
debugPrint('$_tag confirmKycSubmission() - 确认提交 KYC');
|
|
|
|
try {
|
|
final response = await _apiClient.post('/user/kyc/level3/confirm');
|
|
debugPrint('$_tag confirmKycSubmission() - 响应: ${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 KycConfirmResponse.fromJson(data);
|
|
} on ApiException {
|
|
rethrow;
|
|
} catch (e) {
|
|
debugPrint('$_tag confirmKycSubmission() - 异常: $e');
|
|
throw ApiException('提交 KYC 失败: $e');
|
|
}
|
|
}
|
|
|
|
// ============ 手机号验证相关 (用于跳过验证的用户补充验证) ============
|
|
|
|
/// 发送手机号验证短信
|
|
Future<void> sendKycVerifySms() async {
|
|
debugPrint('$_tag sendKycVerifySms() - 发送 KYC 手机验证短信');
|
|
|
|
try {
|
|
final response = await _apiClient.post('/user/kyc/send-phone-sms');
|
|
debugPrint('$_tag sendKycVerifySms() - 响应: ${response.statusCode}');
|
|
} on ApiException {
|
|
rethrow;
|
|
} catch (e) {
|
|
debugPrint('$_tag sendKycVerifySms() - 异常: $e');
|
|
throw ApiException('发送验证码失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 验证手机号验证码
|
|
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<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');
|
|
}
|
|
}
|
|
|
|
/// 验证旧手机验证码
|
|
/// 返回包含 changePhoneToken 和 wasAlreadyVerified 的响应
|
|
Future<VerifyOldPhoneResponse> 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 VerifyOldPhoneResponse.fromJson(data);
|
|
} 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');
|
|
}
|
|
}
|
|
}
|