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:
hailin 2025-12-24 23:54:13 -08:00
parent e4f27b3134
commit 181d11d656
3 changed files with 38 additions and 18 deletions

View File

@ -211,15 +211,16 @@ export class KycApplicationService {
/**
* ========================================
* 层级1: 实名认证 -
* 层级1: 实名认证 -
* ========================================
* ++
*/
async submitRealNameVerification(
userId: string,
realName: 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();
if (!config.level1Enabled) {
@ -231,6 +232,7 @@ export class KycApplicationService {
select: {
realNameVerified: true,
kycStatus: true,
phoneNumber: true,
},
});
@ -242,6 +244,10 @@ export class KycApplicationService {
throw new ApplicationError('实名认证已完成,无需重复提交');
}
if (!user.phoneNumber) {
throw new ApplicationError('未绑定手机号,无法进行实名认证');
}
// 生成请求 ID
const requestId = `REAL_NAME_${userId}_${Date.now()}`;
@ -249,22 +255,24 @@ export class KycApplicationService {
const attempt = await this.prisma.kycVerificationAttempt.create({
data: {
userId: BigInt(userId),
verificationType: 'ID_CARD',
verificationType: 'ID_CARD_THREE',
provider: 'ALIYUN',
requestId,
inputData: {
realName: this.maskName(realName),
idCardNumber: this.maskIdCard(idCardNumber),
phoneNumber: this.maskPhoneNumber(user.phoneNumber),
},
status: 'PENDING',
},
});
try {
// 调用阿里云要素验证
// 调用阿里云要素验证
const result = await this.aliyunKycProvider.verifyIdCard(
realName,
idCardNumber,
user.phoneNumber,
requestId,
);

View File

@ -53,7 +53,7 @@ export interface IdCardOcrResult {
/**
* -
*
* 层级1: 实名认证 - +
* 层级1: 实名认证 - ++
* 层级2: 实人认证 -
* 层级3: KYC - OCR识别验证
*
@ -83,32 +83,34 @@ export class AliyunKycProvider {
/**
* ========================================
* 层级1: 实名认证 -
* 层级1: 实名认证 -
* ========================================
*
*
*/
async verifyIdCard(
realName: string,
idCardNumber: string,
phoneNumber: string,
requestId: string,
): 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) {
this.logger.warn('[AliyunKYC] KYC is disabled, using mock verification');
return this.mockIdCardVerification(realName, idCardNumber);
return this.mockIdCardVerification(realName, idCardNumber, phoneNumber);
}
try {
// 调用阿里云身份要素核验 API
// 调用阿里云身份要素核验 API
const params = {
Action: 'VerifyMaterial',
Version: '2019-03-07',
Format: 'JSON',
BizType: 'ID_CARD_TWO',
BizType: 'ID_CARD_THREE',
Name: realName,
IdCardNumber: idCardNumber,
PhoneNumber: phoneNumber,
};
const response = await this.callAliyunApi(params);
@ -410,8 +412,8 @@ export class AliyunKycProvider {
// ============ Mock 方法 (开发/测试环境) ============
private mockIdCardVerification(realName: string, idCardNumber: string): IdCardVerificationResult {
this.logger.log('[AliyunKYC] Using mock ID card verification');
private mockIdCardVerification(realName: string, idCardNumber: string, phoneNumber: string): IdCardVerificationResult {
this.logger.log('[AliyunKYC] Using mock ID card verification (3-factor)');
// 基本格式验证
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 {
success: true,
rawResponse: { mock: true, verifyTime: new Date().toISOString() },
rawResponse: { mock: true, verifyTime: new Date().toISOString(), verifyType: '3-factor' },
};
}

View File

@ -4,7 +4,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:go_router/go_router.dart';
import 'kyc_entry_page.dart';
/// KYC 1: ()
/// KYC 1: (: ++)
class KycIdPage extends ConsumerStatefulWidget {
const KycIdPage({super.key});
@ -143,7 +143,7 @@ class _KycIdPageState extends ConsumerState<KycIdPage> {
SizedBox(height: 24.h),
Text(
'要素验证',
'要素验证',
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w700,
@ -152,7 +152,7 @@ class _KycIdPageState extends ConsumerState<KycIdPage> {
),
SizedBox(height: 8.h),
Text(
'请填写与身份证一致的信息',
'请填写与身份证一致的信息,系统将自动使用您的注册手机号进行验证',
style: TextStyle(
fontSize: 14.sp,
color: const Color(0xFF999999),
@ -224,7 +224,7 @@ class _KycIdPageState extends ConsumerState<KycIdPage> {
SizedBox(width: 8.w),
Expanded(
child: Text(
'您的身份信息将通过权威数据源进行验证,信息将被加密存储,不会泄露给任何第三方。',
'您的身份信息(姓名+身份证号+手机号)将通过权威数据源进行三要素验证,信息将被加密存储,不会泄露给任何第三方。',
style: TextStyle(
fontSize: 12.sp,
color: const Color(0xFFE65100),