feat(kyc): 升级实名认证为三要素验证(姓名+身份证号+手机号)
- 后端 aliyun-kyc.provider.ts: 改用 ID_CARD_THREE 类型,添加 PhoneNumber 参数 - 后端 kyc-application.service.ts: 从用户账户获取手机号传递给 KYC provider - 前端 kyc_id_page.dart: 更新文案为"三要素验证" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e4f27b3134
commit
181d11d656
|
|
@ -211,15 +211,16 @@ export class KycApplicationService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ========================================
|
* ========================================
|
||||||
* 层级1: 实名认证 - 提交二要素验证
|
* 层级1: 实名认证 - 提交三要素验证
|
||||||
* ========================================
|
* ========================================
|
||||||
|
* 验证姓名+身份证号+手机号(手机号从用户账户获取)
|
||||||
*/
|
*/
|
||||||
async submitRealNameVerification(
|
async submitRealNameVerification(
|
||||||
userId: string,
|
userId: string,
|
||||||
realName: string,
|
realName: string,
|
||||||
idCardNumber: string,
|
idCardNumber: string,
|
||||||
) {
|
) {
|
||||||
this.logger.log(`[KYC] [Level1] Submitting real name verification for user: ${userId}`);
|
this.logger.log(`[KYC] [Level1] Submitting real name verification (3-factor) for user: ${userId}`);
|
||||||
|
|
||||||
const config = await this.getKycConfig();
|
const config = await this.getKycConfig();
|
||||||
if (!config.level1Enabled) {
|
if (!config.level1Enabled) {
|
||||||
|
|
@ -231,6 +232,7 @@ export class KycApplicationService {
|
||||||
select: {
|
select: {
|
||||||
realNameVerified: true,
|
realNameVerified: true,
|
||||||
kycStatus: true,
|
kycStatus: true,
|
||||||
|
phoneNumber: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -242,6 +244,10 @@ export class KycApplicationService {
|
||||||
throw new ApplicationError('实名认证已完成,无需重复提交');
|
throw new ApplicationError('实名认证已完成,无需重复提交');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!user.phoneNumber) {
|
||||||
|
throw new ApplicationError('未绑定手机号,无法进行实名认证');
|
||||||
|
}
|
||||||
|
|
||||||
// 生成请求 ID
|
// 生成请求 ID
|
||||||
const requestId = `REAL_NAME_${userId}_${Date.now()}`;
|
const requestId = `REAL_NAME_${userId}_${Date.now()}`;
|
||||||
|
|
||||||
|
|
@ -249,22 +255,24 @@ export class KycApplicationService {
|
||||||
const attempt = await this.prisma.kycVerificationAttempt.create({
|
const attempt = await this.prisma.kycVerificationAttempt.create({
|
||||||
data: {
|
data: {
|
||||||
userId: BigInt(userId),
|
userId: BigInt(userId),
|
||||||
verificationType: 'ID_CARD',
|
verificationType: 'ID_CARD_THREE',
|
||||||
provider: 'ALIYUN',
|
provider: 'ALIYUN',
|
||||||
requestId,
|
requestId,
|
||||||
inputData: {
|
inputData: {
|
||||||
realName: this.maskName(realName),
|
realName: this.maskName(realName),
|
||||||
idCardNumber: this.maskIdCard(idCardNumber),
|
idCardNumber: this.maskIdCard(idCardNumber),
|
||||||
|
phoneNumber: this.maskPhoneNumber(user.phoneNumber),
|
||||||
},
|
},
|
||||||
status: 'PENDING',
|
status: 'PENDING',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 调用阿里云二要素验证
|
// 调用阿里云三要素验证
|
||||||
const result = await this.aliyunKycProvider.verifyIdCard(
|
const result = await this.aliyunKycProvider.verifyIdCard(
|
||||||
realName,
|
realName,
|
||||||
idCardNumber,
|
idCardNumber,
|
||||||
|
user.phoneNumber,
|
||||||
requestId,
|
requestId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export interface IdCardOcrResult {
|
||||||
/**
|
/**
|
||||||
* 阿里云实人认证服务 - 支持三层认证
|
* 阿里云实人认证服务 - 支持三层认证
|
||||||
*
|
*
|
||||||
* 层级1: 实名认证 - 二要素验证(姓名+身份证号)
|
* 层级1: 实名认证 - 三要素验证(姓名+身份证号+手机号)
|
||||||
* 层级2: 实人认证 - 人脸活体检测
|
* 层级2: 实人认证 - 人脸活体检测
|
||||||
* 层级3: KYC - 证件照OCR识别验证
|
* 层级3: KYC - 证件照OCR识别验证
|
||||||
*
|
*
|
||||||
|
|
@ -83,32 +83,34 @@ export class AliyunKycProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ========================================
|
* ========================================
|
||||||
* 层级1: 实名认证 - 二要素验证
|
* 层级1: 实名认证 - 三要素验证
|
||||||
* ========================================
|
* ========================================
|
||||||
* 验证姓名和身份证号是否匹配
|
* 验证姓名、身份证号和手机号是否匹配
|
||||||
*/
|
*/
|
||||||
async verifyIdCard(
|
async verifyIdCard(
|
||||||
realName: string,
|
realName: string,
|
||||||
idCardNumber: string,
|
idCardNumber: string,
|
||||||
|
phoneNumber: string,
|
||||||
requestId: string,
|
requestId: string,
|
||||||
): Promise<IdCardVerificationResult> {
|
): Promise<IdCardVerificationResult> {
|
||||||
this.logger.log(`[AliyunKYC] [Level1] Starting ID card verification, requestId: ${requestId}`);
|
this.logger.log(`[AliyunKYC] [Level1] Starting ID card verification (3-factor), requestId: ${requestId}`);
|
||||||
|
|
||||||
// 开发/测试环境:模拟验证
|
// 开发/测试环境:模拟验证
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
this.logger.warn('[AliyunKYC] KYC is disabled, using mock verification');
|
this.logger.warn('[AliyunKYC] KYC is disabled, using mock verification');
|
||||||
return this.mockIdCardVerification(realName, idCardNumber);
|
return this.mockIdCardVerification(realName, idCardNumber, phoneNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 调用阿里云身份二要素核验 API
|
// 调用阿里云身份三要素核验 API
|
||||||
const params = {
|
const params = {
|
||||||
Action: 'VerifyMaterial',
|
Action: 'VerifyMaterial',
|
||||||
Version: '2019-03-07',
|
Version: '2019-03-07',
|
||||||
Format: 'JSON',
|
Format: 'JSON',
|
||||||
BizType: 'ID_CARD_TWO',
|
BizType: 'ID_CARD_THREE',
|
||||||
Name: realName,
|
Name: realName,
|
||||||
IdCardNumber: idCardNumber,
|
IdCardNumber: idCardNumber,
|
||||||
|
PhoneNumber: phoneNumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await this.callAliyunApi(params);
|
const response = await this.callAliyunApi(params);
|
||||||
|
|
@ -410,8 +412,8 @@ export class AliyunKycProvider {
|
||||||
|
|
||||||
// ============ Mock 方法 (开发/测试环境) ============
|
// ============ Mock 方法 (开发/测试环境) ============
|
||||||
|
|
||||||
private mockIdCardVerification(realName: string, idCardNumber: string): IdCardVerificationResult {
|
private mockIdCardVerification(realName: string, idCardNumber: string, phoneNumber: string): IdCardVerificationResult {
|
||||||
this.logger.log('[AliyunKYC] Using mock ID card verification');
|
this.logger.log('[AliyunKYC] Using mock ID card verification (3-factor)');
|
||||||
|
|
||||||
// 基本格式验证
|
// 基本格式验证
|
||||||
if (!realName || realName.length < 2) {
|
if (!realName || realName.length < 2) {
|
||||||
|
|
@ -441,9 +443,19 @@ export class AliyunKycProvider {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 手机号格式验证
|
||||||
|
const phoneRegex = /^1[3-9]\d{9}$/;
|
||||||
|
if (!phoneNumber || !phoneRegex.test(phoneNumber)) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
errorMessage: '手机号格式不正确',
|
||||||
|
rawResponse: { mock: true, reason: 'invalid_phone' },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
rawResponse: { mock: true, verifyTime: new Date().toISOString() },
|
rawResponse: { mock: true, verifyTime: new Date().toISOString(), verifyType: '3-factor' },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'kyc_entry_page.dart';
|
import 'kyc_entry_page.dart';
|
||||||
|
|
||||||
/// KYC 层级1: 实名认证页面 (二要素验证)
|
/// KYC 层级1: 实名认证页面 (三要素验证: 姓名+身份证号+手机号)
|
||||||
class KycIdPage extends ConsumerStatefulWidget {
|
class KycIdPage extends ConsumerStatefulWidget {
|
||||||
const KycIdPage({super.key});
|
const KycIdPage({super.key});
|
||||||
|
|
||||||
|
|
@ -143,7 +143,7 @@ class _KycIdPageState extends ConsumerState<KycIdPage> {
|
||||||
SizedBox(height: 24.h),
|
SizedBox(height: 24.h),
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
'二要素验证',
|
'三要素验证',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24.sp,
|
fontSize: 24.sp,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
|
|
@ -152,7 +152,7 @@ class _KycIdPageState extends ConsumerState<KycIdPage> {
|
||||||
),
|
),
|
||||||
SizedBox(height: 8.h),
|
SizedBox(height: 8.h),
|
||||||
Text(
|
Text(
|
||||||
'请填写与身份证一致的信息',
|
'请填写与身份证一致的信息,系统将自动使用您的注册手机号进行验证',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
color: const Color(0xFF999999),
|
color: const Color(0xFF999999),
|
||||||
|
|
@ -224,7 +224,7 @@ class _KycIdPageState extends ConsumerState<KycIdPage> {
|
||||||
SizedBox(width: 8.w),
|
SizedBox(width: 8.w),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'您的身份信息将通过权威数据源进行验证,信息将被加密存储,不会泄露给任何第三方。',
|
'您的身份信息(姓名+身份证号+手机号)将通过权威数据源进行三要素验证,信息将被加密存储,不会泄露给任何第三方。',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
color: const Color(0xFFE65100),
|
color: const Color(0xFFE65100),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue