鸿蒙应用内嵌H5如何唤起支付宝支付
结论
在鸿蒙应用的内嵌 H5 中唤起支付宝,并不能通过 Web 组件的一项配置直接实现。具体接入方式取决于所使用的支付产品:
- 如果使用“手机网站支付”,服务端调用
alipay.trade.wap.pay,H5 再提交支付宝返回的支付表单。Web 组件还要正确处理支付宝页面的跳转以及支付后的回跳。 - 如果使用“App 支付”,H5 通过 JSBridge 通知鸿蒙原生层,由原生层调用支付宝为 HarmonyOS 提供的支付 SDK。支付订单必须在服务端签名,应用私钥不能保存在 H5 或鸿蒙客户端中。
对于应用内嵌页面,通常更适合采用第二种方式,因为支付流程和结果回传更容易控制。不过,当前接入的支付宝产品必须提供适用于目标 HarmonyOS 版本的客户端 SDK,具体支持情况应以接入时的支付宝开放平台文档和 SDK 为准。
为什么不能直接在 H5 中调用 App 支付
支付宝 App 支付需要客户端 SDK 配合。普通 H5 页面无法安全保存商户私钥,也不能直接调用鸿蒙原生 SDK。
即使 H5 获得了类似 alipays:// 的跳转地址,Web 组件也不一定会像系统浏览器那样将地址交给支付宝应用处理。实际行为可能受到系统版本、Web 内核和支付宝客户端版本的影响,因此,拼接支付宝 URL Scheme 不能作为稳定的支付方案。
各端可以按以下方式分工:
- H5 请求业务服务端创建订单。
- 服务端调用支付宝接口并完成签名。
- H5 将服务端返回的订单字符串传给鸿蒙原生层。
- 原生层调用支付宝 SDK。
- 服务端接收支付宝异步通知,确认最终支付状态。
- 客户端查询服务端的订单状态并更新页面。
基础权限配置
Web 页面需要访问网络,因此要在模块的 module.json5 中声明网络权限:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
如果页面、支付接口或回调地址使用 HTTP,还要处理明文流量限制。正式环境应统一使用 HTTPS,不建议为支付场景开放全局明文访问。
推荐方案:H5 通过 JSBridge 调用原生支付
H5 发起支付
H5 先请求自己的服务端创建订单。服务端返回支付宝 SDK 所需的订单字符串后,H5 调用鸿蒙侧注入的 JSBridge:
async function startAlipay(orderId) {
const response = await fetch('/api/payment/alipay/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ orderId })
});
if (!response.ok) {
throw new Error('创建支付订单失败');
}
const data = await response.json();
if (!window.HarmonyPayBridge) {
throw new Error('当前环境不支持原生支付宝支付');
}
// orderString 由服务端生成并签名
window.HarmonyPayBridge.pay(data.orderString);
}
window.onAlipayResult = async function (result) {
// 原生结果只能用于改善交互,不能作为到账依据
console.log('支付宝客户端返回结果:', result);
// 重新向业务服务端查询订单状态
const response = await fetch(
`/api/payment/status?orderId=${encodeURIComponent(result.orderId)}`
);
const payment = await response.json();
if (payment.status === 'PAID') {
showPaymentSuccess();
} else {
showPaymentPending();
}
};
支付宝应用私钥、签名逻辑和商户敏感配置都不能放进这段代码。
鸿蒙 Web 组件注入桥接对象
下面是 JSBridge 的基本结构。payWithAlipaySdk 是业务侧的封装方法,需要在其中调用实际接入的支付宝 HarmonyOS SDK,并不是支付宝官方 API 的名称。
import { webview } from '@kit.ArkWeb';
class HarmonyPayBridge {
private onPayRequest: (orderString: string) => void;
constructor(onPayRequest: (orderString: string) => void) {
this.onPayRequest = onPayRequest;
}
pay(orderString: string): void {
if (!orderString) {
return;
}
this.onPayRequest(orderString);
}
}
@Entry
@Component
struct PaymentPage {
private controller: webview.WebviewController =
new webview.WebviewController();
private bridge: HarmonyPayBridge = new HarmonyPayBridge(
(orderString: string) => {
this.startAlipay(orderString);
}
);
private async startAlipay(orderString: string): Promise<void> {
try {
// 此处调用实际接入的支付宝 HarmonyOS SDK
const result = await payWithAlipaySdk(orderString);
const callbackData = JSON.stringify({
orderId: result.orderId,
resultStatus: result.resultStatus
});
await this.controller.runJavaScript(
`window.onAlipayResult(${callbackData})`
);
} catch (error) {
const callbackData = JSON.stringify({
resultStatus: 'SDK_ERROR'
});
await this.controller.runJavaScript(
`window.onAlipayResult(${callbackData})`
);
}
}
build() {
Web({
src: 'https://example.com/payment',
controller: this.controller
})
.javaScriptAccess(true)
.javaScriptProxy({
object: this.bridge,
name: 'HarmonyPayBridge',
methodList: ['pay'],
controller: this.controller
});
}
}
这段示例主要说明 H5 与原生层之间的调用关系。支付宝 SDK 的包名、初始化方法、支付方法和返回类型都可能随版本变化,不能把示例中的 payWithAlipaySdk 直接当作官方接入代码使用。
JSBridge 也不能向任意网页开放。至少要检查当前 Web 页面的域名,只允许受信任的业务页面调用支付方法。
服务端生成支付订单
采用 App 支付时,服务端通常调用 alipay.trade.app.pay。下面是 Java 侧的结构示例:
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl,
appId,
privateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo(outTradeNo);
model.setSubject(subject);
model.setTotalAmount(totalAmount);
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
request.setNotifyUrl("https://api.example.com/payment/alipay/notify");
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
if (!response.isSuccess()) {
throw new IllegalStateException("支付宝订单创建失败");
}
String orderString = response.getBody();
客户端只接收最终的 orderString。商户私钥必须保存在服务端,并通过密钥管理、访问控制和日志脱敏等措施加以保护。
可选方案:使用手机网站支付
如果商户申请的是手机网站支付,服务端应调用 alipay.trade.wap.pay:
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo(outTradeNo);
model.setSubject(subject);
model.setTotalAmount(totalAmount);
model.setProductCode("QUICK_WAP_WAY");
request.setBizModel(model);
request.setReturnUrl("https://m.example.com/payment/result");
request.setNotifyUrl("https://api.example.com/payment/alipay/notify");
String formHtml = alipayClient.pageExecute(request).getBody();
formHtml 是支付宝返回的提交表单。服务端可以直接将其输出为 HTML 页面,也可以交给受信任的 H5 页面渲染并提交:
<div id="alipay-form"></div>
<script>
async function submitWapPayment(orderId) {
const response = await fetch(
`/api/payment/alipay/wap-form?orderId=${encodeURIComponent(orderId)}`
);
const formHtml = await response.text();
const container = document.getElementById('alipay-form');
container.innerHTML = formHtml;
const form = container.querySelector('form');
if (!form) {
throw new Error('未获取到支付宝支付表单');
}
form.submit();
}
</script>
支付宝生成的页面之后可能尝试打开支付宝客户端。鸿蒙 Web 组件能否顺利处理这次跳转,需要在目标设备和相应版本的支付宝客户端上实际验证。如果内嵌 Web 的兼容性不理想,可以改用系统浏览器打开 WAP 支付页,再通过 HTTPS App Link 或业务查询接口恢复支付结果页。
支付结果必须由服务端确认
支付宝 SDK 返回成功,或者 H5 跳转到了 return_url,都不表示服务端已经确认收款。支付结果应按以下流程处理:
- 服务端接收
notify_url的异步通知。 - 按照支付宝文档验证通知签名。
- 校验
app_id、商户身份、订单号、金额和币种等字段。 - 通过幂等逻辑更新订单,避免重复通知触发重复处理。
- 客户端向业务服务端查询订单状态。
客户端收到的 resultStatus 只能用来展示“正在确认”或“用户取消”等即时状态,不能直接决定发货、充值或开通权益。
注意事项
- 不要在客户端自行拼接或长期依赖
alipays://地址。支付宝生成的跳转参数和兼容策略可能发生调整。 - 不要把
return_url当作支付成功凭据。最终状态应以服务端验签后的异步通知或主动查询结果为准。 - JSBridge 必须限制来源域名,并检查参数长度、格式和当前登录用户。
- 创建支付订单时,服务端应重新读取商品价格,不能信任 H5 上传的金额。
out_trade_no必须唯一,异步通知处理必须具备幂等性。- 回调页面需要处理应用切到后台、支付宝未安装、用户取消、网络中断以及支付结果尚未同步等情况。
- 上线前至少要测试真机支付、取消支付、重复点击、应用被系统回收、通知重复到达和回跳失败等场景。
- HarmonyOS 和支付宝 SDK 的接口可能继续更新。最终使用的包依赖、签名配置和调用方法,应以接入时的支付宝开放平台文档为准。
备注:内容仅供参考。
