查看: 155|回复: 3

Payment Kit 身份核验实战:API 24 实名验证与人脸核身 PC 端适配

[复制链接]
发表于 4 小时前 | 显示全部楼层 |阅读模式
在 HarmonyOS NEXT 6.1.1(API 24)中,Payment Kit 的 realNameService 服务迎来重要升级:人脸核身能力正式支持 PC 及 2in1 设备,同时元服务(Atomic Service)也可免安装调用身份核验接口。这对于金融、政务、医疗等强合规场景的身份真实性验证具有重要意义。本文介绍实名验证、实名授权、人脸核身三大防护矩阵,并给出完整的 ETS 代码实现。

一、身份验证三大防护矩阵
realNameService 提供三层递进的安全验证机制:
实名信息验证(startRealNameVerification):校验用户输入的姓名+身份证号是否与支付底座预留信息一致,适用于游戏防沉迷、政务预约等无需留存证件原件的场景。
实名信息授权(startRealNameAuth):直接从系统支付底座提取已认证的姓名、证件号,适用于金融开户、医疗建档等需要快速录入实名数据的场景。
人脸核身实人验证(startFaceVerification):通过摄像头采集面部数据,结合 TEE 活体检测算法验证操作者为本人,适用于大额转账、修改密保等高风险操作。
二、PC/2in1 设备的新特性打通
API 24 通过多端一致性硬件抽象层(HAL),将 PC 及 2in1 设备的安全摄像头与底层 TEE 安全微内核打通,防止注入虚假相机流、翻拍、AI 换脸等攻击。人脸核身组件在 PC 端同样运行在系统级安全渲染层,商户应用无法截图、录屏或窃听输入。同时,身份核验接口全面支持元服务免安装拉起,降低了轻量级政务或缴费元服务的集成门槛。
三、直连商户核身接入流程
商户服务端先向华为支付网关发起预验证请求,获取具有时效性的 preVerifyId;客户端通过 UIAbilityContext 调用 realNameService 接口(如 startFaceVerification);用户完成核身同意后,Promise 返回 verifyResultId 或 realNameAuthId;商户服务端再向支付网关核对该凭证,最终确认身份。整套流程确保一过性安全。
四、ETS 完整代码实现
以下代码实现了一个身份核验控制舱页面,包含三个核心按钮:实名验证、实名授权、人脸核身。注意需要在正式工程中替换预验证 ID 并适配 UIAbilityContext。错误码处理统一封装在 handlePaymentErrorCode 方法中。
  1. import { router } from '@kit.ArkUI';
  2. import { hilog } from '@kit.PerformanceAnalysisKit';
  3. import { BusinessError } from '@kit.BasicServicesKit';
  4. import { realNameService } from '@kit.PaymentKit';
  5. import { common } from '@kit.AbilityKit';
  6. const TAG = 'PaymentKitRealNameDetail';
  7. const DOMAIN_NUMBER = 0xFF00;
  8. class PaymentLogEntry {
  9.   timestamp: string = '';
  10.   message: string = '';
  11.   type: string = '';
  12.   constructor(timestamp: string, message: string, type: string) {
  13.     this.timestamp = timestamp;
  14.     this.message = message;
  15.     this.type = type;
  16.   }
  17. }
  18. @Entry
  19. @Component
  20. struct PaymentKitRealNameDetail {
  21.   @State preVerifyIdInput: string = 'your_pre_verify_id_2026_demo';
  22.   @State faceVerifyIdInput: string = 'your_face_pre_verify_id_2026_demo';
  23.   @State isVerifyingName: boolean = false;
  24.   @State isAuthorizingName: boolean = false;
  25.   @State isVerifyingFace: boolean = false;
  26.   @State scanningLineOffsetY: number = 0;
  27.   @State isScanning: boolean = false;
  28.   @State consoleLogs: PaymentLogEntry[] = [];
  29.   aboutToAppear() {
  30.     this.pushLog('🎬 Payment Kit 身份核验控制舱已就位', 'i');
  31.     this.pushLog('🛡️ 系统能力监测:SystemCapability.Payment.RealNameService [API 24]', 'i');
  32.     this.pushLog('💻 新增特性:人脸核身实人验证能力已全面覆盖 PC/2in1 设备', 'i');
  33.   }
  34.   private pushLog(msg: string, type: string = 'i') {
  35.     const time = new Date().toLocaleTimeString();
  36.     const entry = new PaymentLogEntry(time, msg, type);
  37.     this.consoleLogs.unshift(entry);
  38.     hilog.i(DOMAIN_NUMBER, TAG, `[${type.toUpperCase()}] ${msg}`);
  39.   }
  40.   private clearLogs() {
  41.     this.consoleLogs = [];
  42.     this.pushLog('🧹 极客终端日志已清理。', 'i');
  43.   }
  44.   private async executeRealNameVerification() {
  45.     if (this.isVerifyingName) return;
  46.     this.isVerifyingName = true;
  47.     this.pushLog(`🚀 准备拉起实名信息验证组件,PreVerifyId: ${this.preVerifyIdInput}`, 'i');
  48.     try {
  49.       const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  50.       if (!context) {
  51.         throw new Error('无法获取 Stage 模型的 UIAbilityContext 上下文信息');
  52.       }
  53.       this.pushLog('⏳ 正在拉起实名验证授权收银台...', 'i');
  54.       const verifyResultId = await realNameService.startRealNameVerification(context, this.preVerifyIdInput);
  55.       this.isVerifyingName = false;
  56.       this.pushLog(`🎉 实名信息验证成功!结果凭证 VerifyResultId: ${verifyResultId}`, 'success');
  57.     } catch (error) {
  58.       this.isVerifyingName = false;
  59.       const err = error as BusinessError;
  60.       this.handlePaymentErrorCode('实名信息验证', err);
  61.     }
  62.   }
  63.   private async executeRealNameAuthorization() {
  64.     if (this.isAuthorizingName) return;
  65.     this.isAuthorizingName = true;
  66.     this.pushLog('🚀 准备拉起实名信息授权组件...', 'i');
  67.     try {
  68.       const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  69.       if (!context) {
  70.         throw new Error('无法获取 Stage 模型的 UIAbilityContext 上下文信息');
  71.       }
  72.       this.pushLog('⏳ 正在拉起实名授权组件...', 'i');
  73.       const realNameAuthId = await realNameService.startRealNameAuth(context);
  74.       this.isAuthorizingName = false;
  75.       this.pushLog(`🎉 实名信息授权成功!结果凭证 RealNameAuthId: ${realNameAuthId}`, 'success');
  76.     } catch (error) {
  77.       this.isAuthorizingName = false;
  78.       const err = error as BusinessError;
  79.       this.handlePaymentErrorCode('实名信息授权', err);
  80.     }
  81.   }
  82.   private async executeFaceVerification() {
  83.     if (this.isVerifyingFace) return;
  84.     this.isVerifyingFace = true;
  85.     this.isScanning = true;
  86.     this.triggerFaceScanningAnimation();
  87.     this.pushLog(`🚀 准备拉起人脸核身实人验证,PreVerifyId: ${this.faceVerifyIdInput}`, 'i');
  88.     try {
  89.       const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  90.       if (!context) {
  91.         throw new Error('无法获取 Stage 模型的 UIAbilityContext 上下文信息');
  92.       }
  93.       this.pushLog('⏳ 正在唤醒本地安全摄像头并加载 TEE 人脸核身组件...', 'i');
  94.       const verifyResultId = await realNameService.startFaceVerification(context, this.faceVerifyIdInput);
  95.       this.isVerifyingFace = false;
  96.       this.isScanning = false;
  97.       this.pushLog(`🎉 人脸核身实人验证成功!结果凭证 VerifyResultId: ${verifyResultId}`, 'success');
  98.     } catch (error) {
  99.       this.isVerifyingFace = false;
  100.       this.isScanning = false;
  101.       const err = error as BusinessError;
  102.       this.handlePaymentErrorCode('人脸核身验证', err);
  103.     }
  104.   }
  105.   private triggerFaceScanningAnimation() {
  106.     if (!this.isScanning) return;
  107.     animateTo({
  108.       duration: 1200,
  109.       curve: Curve.EaseInOut,
  110.       onFinish: () => {
  111.         if (this.isScanning) {
  112.           animateTo({
  113.             duration: 1200,
  114.             curve: Curve.EaseInOut,
  115.             onFinish: () => {
  116.               this.triggerFaceScanningAnimation();
  117.             }
  118.           }, () => {
  119.             this.scanningLineOffsetY = 0;
  120.           })
  121.         }
  122.       }
  123.     }, () => {
  124.       this.scanningLineOffsetY = 110;
  125.     })
  126.   }
  127.   private handlePaymentErrorCode(action: string, error: BusinessError) {
  128.     const errCode = error.code;
  129.     const errMsg = error.message;
  130.     if (errCode === 1020100000) {
  131.       this.pushLog(`❌ ${action}失败[1020100000]: 当前设备不支持此安全Kit能力(如模拟器或部分无前置安全相机PC)!`, 'error');
  132.     } else if (errCode === 1020100001) {
  133.       this.pushLog(`❌ ${action}失败[1020100001]: 用户未接受合规认证或人脸核身协议!`, 'warning');
  134.     } else if (errCode === 1020100002) {
  135.       this.pushLog(`❌ ${action}中断[1020100002]: 用户手动取消了核身或授权流程。`, 'warning');
  136.     } else if (errCode === 1020100003) {
  137.       this.pushLog(`❌ ${action}拦截[1020100003]: PreVerifyId 预验证 ID 格式非法或已过期!`, 'error');
  138.     } else if (errCode === 1020100004) {
  139.       this.pushLog(`❌ ${action}熔断[1020100004]: 当前网络断开,无法拉起核身校验通道。`, 'error');
  140.     } else if (errCode === 1020100005) {
  141.       this.pushLog(`❌ ${action}故障[1020100005]: 系统服务内部致命错误。`, 'error');
  142.     } else if (errCode === 1020100006) {
  143.       this.pushLog(`❌ ${action}被拒[1020100006]: 核心相机组件权限(ohos.permission.CAMERA)未被授予!`, 'error');
  144.     } else if (errCode === 1020100007) {
  145.       this.pushLog(`❌ ${action}失败[1020100007]: 活体检测未通过(翻拍拦截)!`, 'error');
  146.     } else if (errCode === 1020100008 || errCode === 1020100009) {
  147.       this.pushLog(`❌ ${action}失败[${errCode}]: AppID/UserID 与预验证服务不一致!`, 'error');
  148.     } else {
  149.       this.pushLog(`❌ ${action}发生异常[${errCode}]: ${errMsg}`, 'error');
  150.     }
  151.   }
  152.   build() {
  153.     // UI 布局(略,详见原文)
  154.   }
  155. }
复制代码

五、错误码速查表
实际调试中,可通过 handlePaymentErrorCode 方法快速定位问题。关键错误码:
1020100000:设备不支持安全 Kit能力(常见于模拟器或无安全相机的 PC)
1020100001:用户未同意协议
1020100002:用户取消
1020100003:preVerifyId 过期或格式错误
1020100006:缺少摄像头权限
1020100007:活体检测未通过(翻拍拦截)

本文内容基于 HarmonyOS NEXT 6.1.1 (API 24) 官方能力编写,开发者可直接复制代码到 DevEco Studio 中运行测试。注意替换预验证 ID 并确保在真机(非模拟器)上运行人脸核身功能。
回复

使用道具 举报

发表于 4 小时前 | 显示全部楼层

Re: Payment Kit 身份核验实战:API 24 实名验证与人脸核身 PC 端适配

感谢分享!这篇实战内容太及时了,我们团队正好在适配PC端的金融核身场景。API 24把人脸核身能力扩展到PC/2in1设备这点很关键,底层TEE防注入也解决了我们之前担心的安全问题。想问一下,startFaceVerification在PC上调用时,摄像头选择和权限处理跟移动端有啥区别吗?还有那个preVerifyId的失效时间一般是多久?盼复!
回复 支持 反对

使用道具 举报

发表于 4 小时前 | 显示全部楼层

Re: Payment Kit 身份核验实战:API 24 实名验证与人脸核身 PC 端适配

感谢楼主分享这么详细的技术实战!Payment Kit 在 API 24 上的升级确实很实用,特别是人脸核身能力覆盖 PC 和 2in1 设备后,金融政务类应用的跨端适配应该能省不少功夫。你提到的三层防护矩阵逻辑清晰,从信息校验到活体检测的递进设计很合理。 代码部分我仔细看了,用 `PaymentLogEntry` 和扫描线动画来展示状态,对调试和用户体验都挺友好。想请教一下:在 PC 端调用 `startFaceVerification` 时,摄像头权限和隐私提示的流程跟手机端有差异吗?另外,元服务免安装调用时,`preVerifyId` 的获取是否也需要在服务端先向支付网关发请求,还是元服务本身可以内置一个临时 ID?期待后续更多实践经验分享。
回复 支持 反对

使用道具 举报

发表于 4 小时前 | 显示全部楼层

Re: Payment Kit 身份核验实战:API 24 实名验证与人脸核身 PC 端适配

感谢楼主分享!PC和2in1设备支持人脸核身确实是个重要更新,对金融和政务场景的覆盖很有价值。代码部分很清晰,不过我看到错误码处理封装在handlePaymentErrorCode里,但示例中没贴出具体实现,能否补充一下常用错误码的对应处理逻辑?另外,预验证ID的获取流程在服务端对接时有没有什么注意事项?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-23 17:31 , Processed in 0.045847 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部