From 4ec4df14b53b0f93fe77991e694de569089d1c61 Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 25 Dec 2025 09:46:17 -0800 Subject: [PATCH] =?UTF-8?q?feat(identity-service):=20KYC=20=E6=94=B9?= =?UTF-8?q?=E7=94=A8=E4=BA=8C=E8=A6=81=E7=B4=A0=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 默认使用阿里云 Id2MetaStandardVerify API(姓名+身份证号) - 保留三要素验证方法 verifyIdCardThreeFactor 作为备用 - 二要素验证始终调用真实 API,不使用 mock - 兼容 BizCode 数字和字符串类型 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../external/kyc/aliyun-kyc.provider.ts | 151 +++++++++++++++++- 1 file changed, 146 insertions(+), 5 deletions(-) diff --git a/backend/services/identity-service/src/infrastructure/external/kyc/aliyun-kyc.provider.ts b/backend/services/identity-service/src/infrastructure/external/kyc/aliyun-kyc.provider.ts index 52f6d00e..aef5458d 100644 --- a/backend/services/identity-service/src/infrastructure/external/kyc/aliyun-kyc.provider.ts +++ b/backend/services/identity-service/src/infrastructure/external/kyc/aliyun-kyc.provider.ts @@ -83,13 +83,117 @@ export class AliyunKycProvider { /** * ======================================== - * 层级1: 实名认证 - 手机号三要素验证 + * 层级1: 实名认证 - 二要素验证(默认) + * ======================================== + * 验证姓名和身份证号是否匹配 + * 使用阿里云 IdCardTwoMetaVerify API (身份证二要素核验) + * 文档: https://help.aliyun.com/zh/id-verification/information-verification/developer-reference/api-cloudauth-2019-03-07-idcardtwometaverify + */ + async verifyIdCard( + realName: string, + idCardNumber: string, + phoneNumber: string, + requestId: string, + ): Promise { + // 默认使用二要素验证(姓名+身份证号) + return this.verifyIdCardTwoFactor(realName, idCardNumber, requestId); + } + + /** + * 二要素验证:姓名 + 身份证号 + * 使用阿里云 IdCardTwoMetaVerify API + */ + async verifyIdCardTwoFactor( + realName: string, + idCardNumber: string, + requestId: string, + ): Promise { + this.logger.log(`[AliyunKYC] [Level1] Starting 2-factor verification, requestId: ${requestId}`); + + // 二要素验证始终使用真实 API,不使用 mock + if (!this.accessKeyId || !this.accessKeySecret) { + this.logger.error('[AliyunKYC] KYC credentials not configured'); + return { + success: false, + errorMessage: '实名认证服务未配置', + rawResponse: { error: 'credentials_not_configured' }, + }; + } + + try { + // 调用阿里云身份二要素标准版核验 API (Id2MetaStandardVerify) + // 文档: https://help.aliyun.com/zh/id-verification/api-cloudauth-2019-03-07-id2metastandardverify-infor-verify + // 参数说明: + // - ParamType: 加密方式 (normal=明文) + // - UserName: 姓名 + // - IdentifyNum: 身份证号 + const params = { + Action: 'Id2MetaStandardVerify', + Version: '2019-03-07', + Format: 'JSON', + ParamType: 'normal', + UserName: realName, + IdentifyNum: idCardNumber, + }; + + const response = await this.callAliyunApi(params); + + this.logger.log(`[AliyunKYC] [Level1] 2-factor API Response: ${JSON.stringify(response, null, 2)}`); + + // Id2MetaStandardVerify 返回结果: + // - Code: 200 表示请求成功 + // - ResultObject.BizCode: 核验结果 (数字类型) + // - 1: 核验一致 (成功) + // - 2: 核验不一致 (失败) + // - 3: 查无记录 (失败) + if (response.Code === 'OK' || response.Code === '200' || response.Code === 200) { + const bizCode = response.ResultObject?.BizCode; + // BizCode 可能是数字或字符串,都要兼容 + const isMatch = bizCode === 1 || bizCode === '1'; + + this.logger.log(`[AliyunKYC] [Level1] 2-factor ResultObject: BizCode=${bizCode}`); + + if (isMatch) { + this.logger.log(`[AliyunKYC] [Level1] 2-factor Verification SUCCESS for requestId: ${requestId}`); + return { + success: true, + rawResponse: response, + }; + } else { + this.logger.warn(`[AliyunKYC] [Level1] 2-factor Verification FAILED (BizCode: ${bizCode})`); + return { + success: false, + errorMessage: '身份信息验证失败', + rawResponse: response, + }; + } + } else { + this.logger.warn(`[AliyunKYC] [Level1] 2-factor API call FAILED - Code: ${response.Code}, Message: ${response.Message}`); + return { + success: false, + errorMessage: '身份信息验证失败', + rawResponse: response, + }; + } + } catch (error) { + this.logger.error(`[AliyunKYC] [Level1] 2-factor API call failed: ${error.message}`, error.stack); + return { + success: false, + errorMessage: '实名认证服务暂时不可用', + rawResponse: { error: error.message }, + }; + } + } + + /** + * ======================================== + * 层级1: 实名认证 - 手机号三要素验证(备用) * ======================================== * 验证姓名、身份证号和手机号是否匹配 * 使用阿里云 Mobile3MetaDetailVerify API (手机号三要素核验详版) * 文档: https://help.aliyun.com/zh/id-verification/information-verification/developer-reference/esf1ff158mxowkk6 */ - async verifyIdCard( + async verifyIdCardThreeFactor( realName: string, idCardNumber: string, phoneNumber: string, @@ -138,12 +242,12 @@ export class AliyunKycProvider { // - 203: 手机号与姓名不一致,但与证件号一致 // - 204: 其他不一致 // - 301: 查无记录 - if (response.Code === 'OK' || response.Code === '200') { + if (response.Code === 'OK' || response.Code === '200' || response.Code === 200) { const bizCode = response.ResultObject?.BizCode; const subCode = response.ResultObject?.SubCode; const isConsistent = response.ResultObject?.IsConsistent; - // BizCode === '1' 表示校验一致(成功) - const isMatch = bizCode === '1'; + // BizCode 可能是数字或字符串,都要兼容 + const isMatch = bizCode === 1 || bizCode === '1'; this.logger.log(`[AliyunKYC] [Level1] ResultObject: BizCode=${bizCode}, SubCode=${subCode}, IsConsistent=${isConsistent}`); @@ -486,6 +590,43 @@ export class AliyunKycProvider { // ============ Mock 方法 (开发/测试环境) ============ + private mockIdCardTwoFactorVerification(realName: string, idCardNumber: string): IdCardVerificationResult { + this.logger.log('[AliyunKYC] Using mock ID card verification (2-factor)'); + + // 基本格式验证 + if (!realName || realName.length < 2) { + return { + success: false, + errorMessage: '身份信息验证失败', + rawResponse: { mock: true, reason: 'invalid_name' }, + }; + } + + // 身份证号格式验证 + const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/; + if (!idCardRegex.test(idCardNumber)) { + return { + success: false, + errorMessage: '身份信息验证失败', + rawResponse: { mock: true, reason: 'invalid_id_card_format' }, + }; + } + + // 校验码验证 + if (!this.validateIdCardChecksum(idCardNumber)) { + return { + success: false, + errorMessage: '身份信息验证失败', + rawResponse: { mock: true, reason: 'invalid_checksum' }, + }; + } + + return { + success: true, + rawResponse: { mock: true, verifyTime: new Date().toISOString(), verifyType: '2-factor' }, + }; + } + private mockIdCardVerification(realName: string, idCardNumber: string, phoneNumber: string): IdCardVerificationResult { this.logger.log('[AliyunKYC] Using mock ID card verification (3-factor)');