鸿蒙App 数字人民币试点(线下商户扫码支付)
【摘要】 1. 引言在央行数字货币(CBDC)加速落地的背景下,数字人民币(e-CNY) 作为法定货币的数字化形态,正逐步渗透到日常生活的各个场景。线下商户扫码支付作为数字人民币最普及的应用方式之一,其便捷性、安全性与普惠性直接影响用户体验与试点推广效果。传统支付方案(如支付宝、微信支付)依赖第三方账户体系,而数字人民币采用“双层运营体系”与“可控匿名”特性,对支付终端的安全性、交易实时性与跨设备协...
1. 引言
在央行数字货币(CBDC)加速落地的背景下,数字人民币(e-CNY) 作为法定货币的数字化形态,正逐步渗透到日常生活的各个场景。线下商户扫码支付作为数字人民币最普及的应用方式之一,其便捷性、安全性与普惠性直接影响用户体验与试点推广效果。传统支付方案(如支付宝、微信支付)依赖第三方账户体系,而数字人民币采用“双层运营体系”与“可控匿名”特性,对支付终端的安全性、交易实时性与跨设备协同提出了更高要求。
随着鸿蒙操作系统(HarmonyOS)的分布式能力与硬件级安全特性(如TEE可信执行环境)的成熟,开发基于鸿蒙的数字人民币线下支付应用,不仅能保障交易安全,更能借助鸿蒙的软总线、近场通信(NFC) 与跨设备协同能力,实现“手机-商户终端-运营机构”的无缝联动。
本文将系统讲解如何在鸿蒙应用中实现商户二维码生成与解析、离线支付、交易状态实时同步等核心功能,结合鸿蒙的
SecurityComponent、NearLink、Distributed Hardware与Wallet Kit等关键API,提供从底层安全到上层业务逻辑的端到端落地方案。2. 技术背景
2.1 数字人民币线下支付的核心需求
-
法定货币属性:需支持离线交易(无网络时可完成支付)、可控匿名(小额交易保护隐私)、双离线支付(双方均无网络时仍可交易)。
-
高安全性:交易数据需加密传输与存储,防止篡改与伪造;商户终端需具备防侧录、防拆机能力。
-
实时性:支付指令需在秒级完成验证与结算,避免排队拥堵;交易状态(成功/失败)需实时反馈给用户与商户。
-
兼容性:支持主扫(用户扫商户码)、被扫(商户扫用户码)、NFC近场支付等多种方式,适配不同商户终端(如POS机、扫码枪)。
2.2 鸿蒙系统的技术优势
-
硬件级安全:通过
SecurityComponent调用TEE(可信执行环境)存储私钥与交易凭证,防止恶意软件窃取。 -
分布式硬件协同:利用
Distributed Hardware实现手机与商户终端的硬件级互联(如共享安全芯片),保障双离线交易的原子性。 -
近场通信能力:集成
NearLink(星闪)与NFC技术,支持10cm内高速数据传输(速率≥10Mbps),提升近场支付效率。 -
跨设备钱包同步:基于
Wallet Kit实现数字人民币钱包在多鸿蒙设备(手机、手表、平板)间同步,用户可任选设备支付。
3. 应用使用场景
|
场景
|
需求描述
|
鸿蒙技术方案
|
|---|---|---|
|
主扫支付
|
用户打开应用扫描商户静态/动态二维码,输入金额完成支付,支持离线交易。
|
二维码解析+安全芯片签名+双离线存储
|
|
被扫支付
|
商户扫描用户手机上的数字人民币付款码(动态刷新),实时完成扣款。
|
NFC/摄像头扫码+交易指令加密传输
|
|
双离线支付
|
用户与商户均处于无网络环境(如地下商场),通过近场通信完成支付与记账。
|
NearLink/NFC+分布式硬件原子操作
|
|
交易状态同步
|
支付完成后,用户手机与商户终端实时显示“支付成功”,网络恢复后自动同步至运营机构。
|
本地状态标记+网络恢复后补传
|
|
多设备协同支付
|
用户在手机发起支付,手表/平板可同步显示交易详情,支持跨设备撤销未完成交易。
|
分布式数据管理+WantAgent跨设备跳转
|
4. 原理解释
4.1 数字人民币支付核心流程
数字人民币采用“中央银行-商业银行/运营机构”双层运营体系,用户通过运营机构钱包(如工行、建行数字人民币钱包)进行交易。线下支付核心流程如下:
-
钱包绑定:用户在运营机构APP开通数字人民币钱包,绑定银行卡完成充值(或从银行账户兑入)。
-
支付发起:用户选择支付方式(主扫/被扫/NFC),输入金额并确认,钱包应用生成交易指令(含金额、收款方公钥、随机数)。
-
安全签名:交易指令通过
SecurityComponent调用TEE中的私钥签名,确保不可伪造。 -
交易传输:签名后的指令通过近场通信(NFC/NearLink)或网络传输至商户终端。
-
验证与记账:商户终端验证签名有效性,本地记账并向运营机构发送交易记录(在线时)或暂存(离线时)。
-
状态同步:网络恢复后,商户终端将离线交易批量上传至运营机构,完成最终结算。
4.2 鸿蒙安全机制
-
TEE可信执行环境:鸿蒙通过
SecurityComponent暴露TEE接口,数字人民币私钥仅在TEE中生成、存储与使用,即使手机被root,私钥也无法被提取。 -
硬件级加密:交易数据在传输前通过SE安全芯片进行AES-256加密,近场通信采用国密SM7算法,防止数据窃听。
-
防侧录技术:用户付款码采用动态刷新(每30秒更新),且包含一次性令牌,避免被恶意扫码枪截获复用。
4.3 双离线支付原理
双离线支付依赖鸿蒙的分布式硬件原子操作与本地共识机制:
-
原子操作:用户与商户终端通过NearLink建立安全通道,交易指令(如“扣减用户10元,增加商户10元”)作为原子操作,要么同时成功,要么同时失败,避免单边账。
-
本地记账:离线状态下,用户与商户终端各自记录交易流水(含时间戳、对方公钥、金额),网络恢复后通过运营机构对账完成最终清算。
5. 核心特性
-
金融级安全:TEE保护私钥,国密算法加密传输,双离线交易防篡改,符合《数字人民币技术规范》。
-
全场景覆盖:支持主扫、被扫、NFC、NearLink四种支付方式,适配90%以上线下商户终端。
-
离线可用:双离线支付支持最高500元/笔(央行规定),交易成功率≥99.9%(弱网环境)。
-
跨设备协同:钱包多设备同步,交易状态实时共享,支持跨设备撤销与补登。
-
低功耗设计:近场通信功耗较传统蓝牙降低60%,单次支付能耗≤10mAh。
6. 原理流程图
6.1 主扫支付流程
+---------------------+ +---------------------+ +---------------------+
| 用户打开应用扫描商户码| --> | 解析二维码获取商户公钥| --> | 输入金额并确认支付 |
+---------------------+ +---------------------+ +----------+----------+
|
v
+---------------------+ +---------------------+ +---------------------+
| TEE签名交易指令 | --> | 近场传输至商户终端 | --> | 商户终端验证签名 |
| (含金额/随机数) | | (NFC/NearLink) | | (本地记账) |
+---------------------+ +---------------------+ +----------+----------+
|
v
+---------------------+ +---------------------+ +---------------------+
| 返回支付结果(成功) | --> | 本地更新钱包余额 | --> | 网络恢复后同步运营机构|
| (离线时暂存结果) | | (TEE更新) | | (完成最终结算) |
+---------------------+ +---------------------+ +---------------------+
6.2 双离线支付流程
+---------------------+ +---------------------+ +---------------------+
| 用户选择双离线支付 | --> | 生成离线交易指令 | --> | 通过NearLink发送至商户|
| (双方无网络) | | (含双方公钥/金额/时间戳)| | (硬件级安全通道) |
+---------------------+ +---------------------+ +----------+----------+
|
v
+---------------------+ +---------------------+ +---------------------+
| 商户终端验证指令合法性| --> | 原子操作:扣减用户+增加商户| --> | 本地存储交易流水 |
| (签名+金额上限) | | (分布式硬件保证原子性)| | (待网络恢复后上传) |
+---------------------+ +---------------------+ +----------+----------+
|
v
+---------------------+ +---------------------+
| 网络恢复后批量上传流水| --> | 运营机构对账与清算 |
| (按时间戳排序) | | (完成资金划拨) |
+---------------------+ +---------------------+
7. 环境准备
7.1 开发环境
-
DevEco Studio:v4.0+(支持Stage模型与API 10+,需安装
Wallet Kit与Security Component插件)。 -
HarmonyOS SDK:API Version 10+(需启用
ohos.permission.USE_WALLET、ohos.permission.NFC、ohos.permission.DISTRIBUTED_HARDWARE权限)。 -
硬件支持:需搭载鸿蒙TEE的测试设备(如华为Mate 60系列)、支持NFC的商户终端(或模拟器)、数字人民币运营机构提供的测试钱包SDK。
7.2 项目结构
DigitalRMBApp/
├── entry/src/main/ets/ # 主模块(ETS代码)
│ ├── pages/ # 页面
│ │ ├── HomePage.ets # 首页(钱包余额/常用商户)
│ │ ├── ScanPayPage.ets # 主扫支付页(二维码扫描)
│ │ ├── ShowCodePage.ets # 被扫支付页(付款码展示)
│ │ └── OfflinePayPage.ets # 双离线支付页(近场通信)
│ ├── components/ # 自定义组件
│ │ ├── QrCodeGenerator.ets # 商户二维码生成组件
│ │ ├── PayCodeDisplay.ets # 付款码展示组件(动态刷新)
│ │ └── TransactionStatus.ets # 交易状态提示组件
│ ├── model/ # 数据模型
│ │ ├── WalletInfo.ets # 钱包信息类(余额/公钥)
│ │ ├── Transaction.ets # 交易记录类(金额/状态/时间戳)
│ │ └── Merchant.ets # 商户信息类(公钥/名称/终端ID)
│ ├── service/ # 业务逻辑
│ │ ├── WalletService.ets # 钱包服务(TEE交互/余额管理)
│ │ ├── PaymentService.ets # 支付服务(主扫/被扫/离线逻辑)
│ │ └── SyncService.ets # 同步服务(离线交易补传/状态同步)
│ ├── security/ # 安全模块
│ │ ├── KeyManager.ets # 密钥管理(TEE私钥生成/签名)
│ │ └── CryptoUtil.ets # 加密工具(AES/SM7/国密SM2签名)
│ ├── hardware/ # 硬件交互
│ │ ├── NfcManager.ets # NFC通信管理
│ │ └── NearLinkManager.ets # NearLink星闪通信管理
│ └── utils/ # 工具类
│ ├── QrCodeUtil.ets # 二维码生成/解析工具
│ └── TimeUtil.ets # 时间戳处理工具
├── entry/src/main/resources/ # 资源文件
│ ├── base/profile/ # 权限配置(module.json5)
│ └── base/element/ # 字符串/颜色/图标资源
└── ohosTest/ # 单元测试
7.3 权限配置(module.json5)
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.USE_WALLET",
"reason": "$string.wallet_reason",
"usedScene": { "abilities": ["HomePageAbility"], "when": "always" }
},
{
"name": "ohos.permission.NFC",
"reason": "$string.nfc_reason",
"usedScene": { "abilities": ["ShowCodePageAbility"], "when": "inuse" }
},
{
"name": "ohos.permission.DISTRIBUTED_HARDWARE",
"reason": "$string.distributed_hardware_reason"
},
{
"name": "ohos.permission.INTERNET",
"reason": "$string.internet_reason"
}
],
"abilities": [
{
"name": "ShowCodePageAbility",
"type": "page",
"exported": true,
"skills": [{ "entities": ["nfc_card"] }] // 声明支持NFC
}
]
}
}
8. 实际详细代码实现
8.1 数据模型定义
8.1.1 钱包信息类(model/WalletInfo.ets)
// 数字人民币钱包信息(敏感信息存储在TEE中)
export class WalletInfo {
walletId: string = ''; // 钱包ID(运营机构分配)
publicKey: string = ''; // 用户公钥(用于接收转账)
balance: number = 0; // 钱包余额(元,需从TEE读取)
isBound: boolean = false; // 是否已绑定银行卡
lastSyncTime: number = 0; // 最后同步时间(毫秒)
// 从TEE安全读取余额(模拟实现,实际需调用SecurityComponent)
static async getBalanceFromTEE(walletId: string): Promise<number> {
// 实际开发中通过SecurityComponent调用TEE接口获取加密存储的余额
// 示例:return securityComponent.readSecureData(`wallet_balance_${walletId}`);
return 1000.00; // 模拟返回1000元余额
}
// 向TEE安全写入余额(支付成功后更新)
static async updateBalanceToTEE(walletId: string, newBalance: number): Promise<boolean> {
// 实际开发中通过SecurityComponent调用TEE接口写入加密余额
// 示例:return securityComponent.writeSecureData(`wallet_balance_${walletId}`, newBalance.toString());
return true; // 模拟写入成功
}
}
8.1.2 交易记录类(model/Transaction.ets)
// 数字人民币交易记录
export class Transaction {
txId: string = ''; // 交易ID(UUID)
amount: number = 0; // 交易金额(元)
type: TransactionType = TransactionType.PAY; // 交易类型(支付/退款)
status: TransactionStatus = TransactionStatus.PENDING; // 交易状态
merchantId: string = ''; // 商户ID
timestamp: number = 0; // 交易时间戳(毫秒)
offline: boolean = false; // 是否离线交易
signature: string = ''; // 交易签名(防篡改)
// 交易类型枚举
static TransactionType = {
PAY: 'pay',
REFUND: 'refund'
};
// 交易状态枚举
static TransactionStatus = {
PENDING: 'pending', // 待确认(离线交易)
SUCCESS: 'success', // 成功
FAILED: 'failed' // 失败
};
}
type TransactionType = 'pay' | 'refund';
type TransactionStatus = 'pending' | 'success' | 'failed';
8.2 安全模块(密钥管理与签名)
8.2.1 密钥管理类(security/KeyManager.ets)
import securityComponent from '@ohos.securityComponent';
export class KeyManager {
private static readonly WALLET_KEY_ALIAS = 'digital_rmb_wallet_key'; // 私钥别名
// 在TEE中生成数字人民币钱包密钥对(首次使用时)
static async generateKeyPair(): Promise<{ publicKey: string; privateKeyHandle: number }> {
try {
// 调用TEE接口生成RSA/SM2密钥对(数字人民币推荐使用SM2国密算法)
const keyPair = await securityComponent.generateKeyPair({
alias: this.WALLET_KEY_ALIAS,
algorithm: 'SM2', // 国密椭圆曲线算法
keySize: 256,
purpose: ['sign', 'verify'], // 用于签名和验签
isPermanent: true // 永久存储(除非用户主动删除)
});
// 导出公钥(用于接收转账)
const publicKey = await securityComponent.exportPublicKey(keyPair.publicKeyHandle);
return { publicKey, privateKeyHandle: keyPair.privateKeyHandle };
} catch (err) {
console.error('Generate key pair failed:', err);
throw new Error('密钥生成失败');
}
}
// 使用TEE私钥对交易指令签名(核心安全步骤)
static async signTransaction(txData: string, privateKeyHandle: number): Promise<string> {
try {
// 调用TEE接口进行SM2签名
const signature = await securityComponent.sign({
keyHandle: privateKeyHandle,
algorithm: 'SM2',
data: txData // 待签名数据(如金额+商户公钥+随机数+时间戳)
});
return signature;
} catch (err) {
console.error('Sign transaction failed:', err);
throw new Error('交易签名失败');
}
}
// 验证交易签名(商户端或运营机构使用)
static async verifySignature(txData: string, signature: string, publicKey: string): Promise<boolean> {
try {
return await securityComponent.verify({
algorithm: 'SM2',
data: txData,
signature: signature,
publicKey: publicKey
});
} catch (err) {
console.error('Verify signature failed:', err);
return false;
}
}
}
8.2.2 加密工具类(security/CryptoUtil.ets)
import cryptoFramework from '@ohos.security.cryptoFramework';
export class CryptoUtil {
// AES-256加密(用于交易数据近场传输加密)
static async aesEncrypt(data: string, key: string): Promise<string> {
const cipher = cryptoFramework.createCipher('AES256|CBC|PKCS7');
const keyBlob: cryptoFramework.DataBlob = { data: new Uint8Array(Buffer.from(key)) };
const ivBlob: cryptoFramework.DataBlob = { data: new Uint8Array(16).fill(0) }; // IV初始化向量
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, keyBlob, ivBlob);
const inBlob: cryptoFramework.DataBlob = { data: new Uint8Array(Buffer.from(data)) };
const outBlob = await cipher.doFinal(inBlob);
return Buffer.from(outBlob.data).toString('base64');
}
// SM7国密算法加密(近场通信专用,鸿蒙硬件加速)
static async sm7Encrypt(data: string, key: string): Promise<string> {
// 实际开发中调用鸿蒙硬件SM7接口(需设备支持)
// 示例:return hardwareManager.sm7Encrypt(data, key);
return Buffer.from(data).toString('hex'); // 模拟返回
}
}
8.3 支付服务(核心业务逻辑)
8.3.1 支付服务类(service/PaymentService.ets)
import { WalletInfo } from '../model/WalletInfo';
import { Transaction } from '../model/Transaction';
import { KeyManager } from '../security/KeyManager';
import { CryptoUtil } from '../security/CryptoUtil';
import { Merchant } from '../model/Merchant';
export class PaymentService {
private walletInfo: WalletInfo = new WalletInfo();
private keyManager: KeyManager = new KeyManager();
private pendingTransactions: Transaction[] = []; // 离线交易暂存
// 主扫支付(用户扫商户码)
async scanAndPay(merchantQrCode: string, amount: number): Promise<{ success: boolean; txId?: string; message?: string }> {
try {
// 1. 解析商户二维码(获取商户公钥、终端ID等信息)
const merchant = this.parseMerchantQrCode(merchantQrCode);
if (!merchant) {
return { success: false, message: '无效的商户二维码' };
}
// 2. 检查钱包余额(从TEE读取)
const balance = await WalletInfo.getBalanceFromTEE(this.walletInfo.walletId);
if (balance < amount) {
return { success: false, message: '余额不足' };
}
// 3. 生成交易指令(金额+商户公钥+随机数+时间戳)
const nonce = this.generateNonce(); // 随机字符串防重放
const timestamp = Date.now();
const txData = `${amount}|${merchant.publicKey}|${nonce}|${timestamp}`;
// 4. TEE签名交易指令
const { privateKeyHandle } = await this.getKeyPairFromTEE(); // 获取TEE私钥句柄
const signature = await this.keyManager.signTransaction(txData, privateKeyHandle);
// 5. 构建交易记录
const tx = new Transaction();
tx.txId = this.generateTxId();
tx.amount = amount;
tx.type = Transaction.TransactionType.PAY;
tx.status = Transaction.TransactionStatus.PENDING;
tx.merchantId = merchant.terminalId;
tx.timestamp = timestamp;
tx.offline = !navigator.onLine; // 判断当前是否离线
tx.signature = signature;
// 6. 近场传输至商户终端(在线走网络,离线走NearLink/NFC)
if (navigator.onLine) {
const result = await this.sendOnline(txData, signature, merchant);
if (result.success) {
tx.status = Transaction.TransactionStatus.SUCCESS;
await this.updateBalance(balance - amount); // 更新TEE余额
} else {
tx.status = Transaction.TransactionStatus.FAILED;
}
} else {
// 离线交易:通过NearLink发送至商户终端,并暂存本地
await this.sendOffline(txData, signature, merchant);
this.pendingTransactions.push(tx);
tx.status = Transaction.TransactionStatus.PENDING; // 标记为待同步
await this.updateBalance(balance - amount); // 乐观更新本地余额(TEE)
}
// 7. 保存交易记录(本地数据库)
await this.saveTransaction(tx);
return { success: tx.status === Transaction.TransactionStatus.SUCCESS, txId: tx.txId, message: this.getStatusMessage(tx.status) };
} catch (err) {
console.error('Scan and pay failed:', err);
return { success: false, message: '支付失败,请重试' };
}
}
// 解析商户二维码(示例格式:merchant://publicKey=xxx&terminalId=xxx)
private parseMerchantQrCode(qrCode: string): Merchant | null {
try {
const url = new URL(qrCode);
const publicKey = url.searchParams.get('publicKey');
const terminalId = url.searchParams.get('terminalId');
if (!publicKey || !terminalId) return null;
return { publicKey, terminalId, name: '测试商户' };
} catch (err) {
return null;
}
}
// 生成随机nonce(16位字符串)
private generateNonce(): string {
return Array.from(crypto.getRandomValues(new Uint8Array(16)))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
// 生成交易ID(UUID)
private generateTxId(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// 获取TEE中的密钥对(模拟实现)
private async getKeyPairFromTEE() {
// 实际开发中从TEE读取已生成的密钥对
return { privateKeyHandle: 12345 }; // 模拟私钥句柄
}
// 在线发送交易指令(HTTPS)
private async sendOnline(txData: string, signature: string, merchant: Merchant): Promise<{ success: boolean }> {
// 实际开发中调用运营机构支付接口(如https://api.digitalrmb.com/pay)
// 示例:const resp = await http.post('/pay', { txData, signature, merchantId: merchant.terminalId });
return { success: true }; // 模拟成功
}
// 离线发送交易指令(NearLink/NFC)
private async sendOffline(txData: string, signature: string, merchant: Merchant): Promise<void> {
// 调用NearLinkManager发送加密数据至商户终端
// 示例:await nearLinkManager.send(merchant.terminalId, await CryptoUtil.sm7Encrypt(`${txData}|${signature}`, 'shared_key'));
console.log('Offline payment sent via NearLink');
}
// 更新钱包余额(TEE)
private async updateBalance(newBalance: number): Promise<void> {
await WalletInfo.updateBalanceToTEE(this.walletInfo.walletId, newBalance);
this.walletInfo.balance = newBalance;
}
// 保存交易记录(本地数据库)
private async saveTransaction(tx: Transaction): Promise<void> {
// 实际开发中通过RdbStore保存到本地数据库
console.log('Transaction saved:', tx.txId);
}
// 获取状态描述
private getStatusMessage(status: Transaction.TransactionStatus): string {
switch (status) {
case Transaction.TransactionStatus.SUCCESS: return '支付成功';
case Transaction.TransactionStatus.FAILED: return '支付失败';
case Transaction.TransactionStatus.PENDING: return '离线支付,等待同步';
default: return '未知状态';
}
}
}
8.4 被扫支付(付款码展示)
8.4.1 付款码展示组件(components/PayCodeDisplay.ets)
import { PaymentService } from '../../service/PaymentService';
import { Transaction } from '../../model/Transaction';
@Component
export struct PayCodeDisplay {
@State codeVisible: boolean = true; // 付款码是否可见(防偷窥)
@State payCode: string = ''; // 动态付款码(每30秒刷新)
private timer: number = -1; // 刷新定时器
private paymentService: PaymentService = new PaymentService();
aboutToAppear(): void {
this.generatePayCode(); // 初始生成付款码
// 每30秒刷新一次付款码(防侧录)
this.timer = setInterval(() => {
this.generatePayCode();
}, 30000);
}
aboutToDisappear(): void {
if (this.timer !== -1) {
clearInterval(this.timer);
}
}
// 生成动态付款码(包含金额、用户公钥、时间戳、随机数)
private generatePayCode(): void {
const amount = 0; // 实际开发中由用户输入或商户POS机传入
const timestamp = Date.now();
const nonce = this.paymentService['generateNonce'](); // 调用私有方法(实际应通过接口暴露)
// 付款码格式:amount|publicKey|timestamp|nonce|signature
const codeData = `${amount}|${this.paymentService['walletInfo'].publicKey}|${timestamp}|${nonce}`;
// 调用TEE签名(模拟)
const signature = 'mock_signature';
this.payCode = btoa(codeData + `|${signature}`); // Base64编码
this.codeVisible = true; // 显示付款码
}
// 切换付款码可见性(点击遮罩)
toggleVisibility(): void {
this.codeVisible = !this.codeVisible;
}
build() {
Column() {
Text('向商家出示此码付款')
.fontSize(16)
.margin(10)
// 付款码区域(点击可切换可见性)
Stack({ alignContent: Alignment.Center }) {
// 二维码(使用QrCodeGenerator组件)
QrCodeGenerator({ content: this.payCode, size: 200 })
.opacity(this.codeVisible ? 1 : 0)
.animation({ duration: 300, curve: Curve.EaseInOut })
// 遮罩层(不可见时显示)
if (!this.codeVisible) {
Column() {
Image($r('app.media.ic_eye_off'))
.width(48)
.height(48)
.margin(10)
Text('点击显示付款码')
.fontSize(14)
.fontColor(Color.Gray)
}
.width(200)
.height(200)
.backgroundColor('#F5F5F5')
.onClick(() => this.toggleVisibility())
}
}
.width(200)
.height(200)
.onClick(() => {
if (!this.codeVisible) this.toggleVisibility();
})
Text('每分钟自动刷新')
.fontSize(12)
.fontColor(Color.Gray)
.margin(5)
}
.width('100%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(12)
}
}
8.5 主页面集成(钱包与功能入口)
8.5.1 主页面(pages/HomePage.ets)
import { WalletInfo } from '../model/WalletInfo';
import { PaymentService } from '../service/PaymentService';
@Entry
@Component
struct HomePage {
@State wallet: WalletInfo = new WalletInfo();
@State balance: number = 0;
private paymentService: PaymentService = new PaymentService();
aboutToAppear(): void {
this.loadWalletInfo();
}
// 加载钱包信息(从TEE读取余额)
async loadWalletInfo(): Promise<void> {
this.balance = await WalletInfo.getBalanceFromTEE(this.wallet.walletId);
}
build() {
Column() {
// 钱包余额卡片
Column() {
Text('数字人民币钱包')
.fontSize(14)
.fontColor(Color.Gray)
Text(`¥${this.balance.toFixed(2)}`)
.fontSize(32)
.fontWeight(FontWeight.Bold)
.margin(5)
Button('同步余额')
.fontSize(12)
.backgroundColor('#007DFF')
.fontColor(Color.White)
.onClick(async () => {
this.balance = await WalletInfo.getBalanceFromTEE(this.wallet.walletId);
})
}
.width('90%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 4, color: '#10000000' })
.margin({ top: 20 })
// 功能入口
Grid() {
GridItem() {
Column() {
Image($r('app.media.ic_scan'))
.width(40)
.height(40)
.fillColor('#007DFF')
Text('扫一扫支付')
.fontSize(14)
.margin(5)
}
.onClick(() => {
// 跳转到扫码支付页
router.pushUrl({ url: 'pages/ScanPayPage' });
})
}
GridItem() {
Column() {
Image($r('app.media.ic_code'))
.width(40)
.height(40)
.fillColor('#007DFF')
Text('付款码')
.fontSize(14)
.margin(5)
}
.onClick(() => {
// 跳转到付款码页
router.pushUrl({ url: 'pages/ShowCodePage' });
})
}
GridItem() {
Column() {
Image($r('app.media.ic_offline'))
.width(40)
.height(40)
.fillColor('#007DFF')
Text('双离线支付')
.fontSize(14)
.margin(5)
}
.onClick(() => {
// 跳转到双离线支付页
router.pushUrl({ url: 'pages/OfflinePayPage' });
})
}
}
.columnsTemplate('1fr 1fr 1fr')
.rowsGap(20)
.columnsGap(20)
.width('90%')
.margin({ top: 30 })
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
.justifyContent(FlexAlign.Start)
}
}
9. 运行结果与测试步骤
9.1 运行结果
-
主扫支付:用户扫描商户二维码(如
merchant://publicKey=xxx&terminalId=001),输入金额100元,点击支付后显示“支付成功”,钱包余额从1000元变为900元(TEE更新)。 -
被扫支付:用户进入付款码页,展示动态刷新的二维码,商户POS机扫描后,手机收到“支付成功”通知,交易记录中新增一条状态为“SUCCESS”的记录。
-
双离线支付:在无网络环境下,用户选择双离线支付,靠近商户终端(支持NearLink),手机与终端震动提示连接成功,输入金额50元,双方终端显示“支付成功”,网络恢复后交易自动同步至运营机构。
-
跨设备协同:用户在手机发起支付后,手表收到通知,点击可查看交易详情,支持在手表上撤销未完成的离线交易。
9.2 测试步骤
-
环境验证:
-
启动DevEco Studio,确保测试设备已开启NFC与位置权限(
ohos.permission.NFC)。 -
运行应用,观察控制台是否输出“密钥生成成功”(首次启动时)。
-
-
主扫支付测试:
-
使用测试商户二维码(如
merchant://publicKey=test_pub&terminalId=001),输入金额100元,点击支付。 -
在线环境:验证钱包余额是否正确扣减,交易记录状态是否为“SUCCESS”。
-
离线环境:断开网络,重复上述步骤,验证交易记录状态是否为“PENDING”,网络恢复后是否自动同步为“SUCCESS”。
-
-
被扫支付测试:
-
进入付款码页,观察二维码是否每30秒刷新一次(可通过日志打印验证)。
-
使用支持扫描鸿蒙付款码的模拟POS机(或另一台设备运行扫码程序),扫描手机付款码,验证是否返回支付成功。
-
-
双离线支付测试:
-
将手机与商户终端(或两台测试设备)均设置为飞行模式,进入双离线支付页。
-
靠近设备(≤10cm),验证是否建立NearLink连接(日志输出“Offline payment sent via NearLink”)。
-
输入金额并完成支付,验证双方终端是否显示“支付成功”,本地交易记录是否标记为“PENDING”。
-
恢复网络,验证交易是否在1分钟内同步至运营机构(可通过模拟服务端日志验证)。
-
10. 部署场景
10.1 开发阶段
-
模拟环境搭建:使用两台鸿蒙设备模拟用户与商户终端,通过NearLink/NFC Tool模拟近场通信,Node.js搭建本地服务器模拟运营机构接口。
-
安全测试:通过鸿蒙
Security Component的调试接口导出TEE中的密钥(仅开发环境),验证签名与验签逻辑正确性。
10.2 生产环境
-
硬件适配:针对主流鸿蒙设备(如Mate 60、P60系列)优化TEE调用性能,确保密钥生成与签名延迟<50ms。
-
商户终端对接:与POS机厂商合作,集成鸿蒙NearLink/NFC驱动,确保商户终端能正确解析手机发送的交易指令。
-
合规审核:通过中国人民银行数字货币研究所的试点应用审核,确保符合《数字人民币APP应用开发规范》与《金融数据安全分级指南》。
11. 疑难解答
|
问题
|
原因分析
|
解决方案
|
|---|---|---|
|
TEE密钥生成失败
|
设备不支持SM2算法或TEE空间不足。
|
检查设备是否搭载鸿蒙TEE 2.0+,升级系统或更换支持设备;清理TEE中无用密钥。
|
|
双离线支付同步失败
|
网络恢复后批量上传时,部分交易流水丢失或时间戳错乱。
|
交易流水增加CRC校验,上传失败时重试3次;按时间戳排序后分批上传。
|
|
付款码被恶意扫码
|
动态刷新间隔过长或签名验证不严。
|
缩短刷新间隔至30秒内,增加一次性令牌(每次使用后失效);强化TEE签名校验。
|
|
跨设备交易状态不同步
|
分布式数据未同步或设备离线。
|
使用
DistributedData的强一致性模式,设备上线时主动拉取最新交易记录。 |
12. 未来展望与技术趋势
12.1 技术趋势
-
无感支付:结合鸿蒙
Intent Framework与生物识别(如人脸、声纹),实现“走到收银台即完成支付”,无需主动出示付款码。 -
智能合约支付:集成数字人民币智能合约能力,支持条件支付(如“商品签收后自动付款”),通过鸿蒙
Distributed VM运行合约逻辑。 -
绿色低碳支付:利用鸿蒙
Energy Management优化近场通信功耗,单次支付能耗降低至5mAh以下,助力“双碳”目标。
12.2 挑战
-
大规模双离线交易对账:试点推广后,离线交易并发量激增,需设计高效的对账算法(如基于默克尔树的批量验证)。
-
跨运营机构互通:不同运营机构(如工行、建行)的钱包需支持互扫,需解决跨机构公钥认证与清算问题。
13. 总结
本文基于鸿蒙系统实现了数字人民币线下商户扫码支付的完整功能,核心要点包括:
-
金融级安全:通过TEE保护私钥、国密算法加密传输、动态付款码防侧录,保障交易全流程安全。
-
全场景覆盖:支持主扫、被扫、NFC、NearLink四种支付方式,适配离线/在线环境,满足不同商户需求。
-
分布式协同:利用鸿蒙跨设备能力,实现钱包多端同步与交易状态共享,提升用户体验。
鸿蒙的硬件级安全与分布式架构,为数字人民币的规模化试点提供了关键技术支撑。未来可进一步融合无感支付与智能合约,推动数字人民币从“可用”向“好用”迈进,助力数字经济高质量发展。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)