支付宝小程序通过jsapi收款后如何实现支付宝app语音播报
结论
通过 my.tradePay 等 JSAPI 完成在线支付时,小程序无法要求商家手机上的支付宝 App 播放“到账语音”。收款语音是支付宝客户端的内置能力,是否播报取决于交易产品、商家账号、客户端设置和支付宝当前规则,不属于 JSAPI 开放功能。
因此,不能把支付宝 App 的语音播报当作 JSAPI 收款成功的通知机制。更可靠的方案是:服务端接收支付宝异步通知,验签并确认交易成功,再向商家端 App、收银设备或语音盒子推送消息,由商家端播放到账语音。
为什么不能依赖前端支付回调播报
小程序调用支付接口后,通常只能获得付款人一侧的支付结果,例如:
my.tradePay({
tradeNO: tradeNo,
success: (result) => {
console.log(result);
},
fail: (error) => {
console.error(error);
}
});
这个回调发生在付款人的小程序中,有以下限制:
- 付款人关闭页面后,回调可能无法继续处理。
- 回调结果不能代替服务端对交易状态的确认。
- 小程序无法控制商家手机上的支付宝 App。
- 商家端支付宝 App 是否播报,由支付宝客户端自行决定。
- 小程序进入后台后,页面脚本通常无法稳定播放语音。
即使付款端显示支付成功,也不应直接依据这个前端结果通知商家。
推荐实现流程
可以按照下面的链路实现:
- 服务端创建支付宝交易,并设置异步通知地址
notify_url。 - 付款人在小程序中通过
my.tradePay完成支付。 - 支付宝向
notify_url发送异步通知。 - 服务端使用支付宝公钥验签。
- 核对
app_id、seller_id、订单号、金额和交易状态。 - 使用
out_trade_no或trade_no做幂等处理。 - 将支付成功消息推送到对应商家的收银端。
- 商家端收到消息后播放“支付宝到账 XX 元”。
- 服务端处理完成后,向支付宝返回纯文本
success。
推送通道可以使用 WebSocket、厂商系统推送、连接消息队列的收银客户端或专用语音播报设备。
服务端处理异步通知
下面以 Java 为例说明主要逻辑。具体的依赖版本、异常类型和配置方式,应以项目使用的支付宝 SDK 文档为准。
@PostMapping("/alipay/notify")
@ResponseBody
public String handleAlipayNotify(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((key, values) -> {
if (values != null && values.length > 0) {
params.put(key, values[0]);
}
});
try {
boolean valid = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!valid) {
return "failure";
}
String appId = params.get("app_id");
String sellerId = params.get("seller_id");
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
// 必须与本系统创建订单时保存的数据核对
Order order = orderService.findByOutTradeNo(outTradeNo);
if (order == null) {
return "failure";
}
if (!expectedAppId.equals(appId)) {
return "failure";
}
if (!expectedSellerId.equals(sellerId)) {
return "failure";
}
BigDecimal notifiedAmount = new BigDecimal(totalAmount);
if (order.getAmount().compareTo(notifiedAmount) != 0) {
return "failure";
}
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
// 内部必须做幂等控制,避免重复通知导致重复播报
boolean firstSuccess = orderService.markPaidIfUnpaid(
outTradeNo,
tradeNo
);
if (firstSuccess) {
merchantPushService.sendPaymentSuccess(
order.getMerchantId(),
outTradeNo,
notifiedAmount
);
}
}
return "success";
} catch (Exception e) {
// 记录必要日志,但不要泄露私钥、完整请求或敏感信息
return "failure";
}
}
支付宝可能重复发送异步通知,因此“更新订单”和“生成播报事件”都必须保证幂等。不能每收到一次通知,就无条件播放一次语音。
商家端播放语音
如果商家使用自有 Android 收银 App,可以在收到服务端推送后调用系统 TTS。下面只展示简化后的逻辑:
public class PaymentSpeaker {
private final TextToSpeech textToSpeech;
public PaymentSpeaker(Context context) {
textToSpeech = new TextToSpeech(context, status -> {
if (status == TextToSpeech.SUCCESS) {
textToSpeech.setLanguage(Locale.SIMPLIFIED_CHINESE);
}
});
}
public void speak(BigDecimal amount, String eventId) {
String message = "支付宝到账"
+ amount.setScale(2, RoundingMode.HALF_UP).toPlainString()
+ "元";
textToSpeech.speak(
message,
TextToSpeech.QUEUE_ADD,
null,
eventId
);
}
public void release() {
textToSpeech.shutdown();
}
}
实际项目还要处理以下问题:
- 设备静音、媒体音量和勿扰模式。
- TTS 初始化失败或缺少中文语音包。
- 推送消息重复、乱序和断线重连。
- 多笔款项同时到账时的语音排队。
- 金额格式及超大金额的朗读方式。
- App 被系统清理后的通知到达率。
- 商户、门店与收银设备之间的绑定关系。
如果商家使用普通小程序作为收银端,还要考虑小程序进入后台后的限制。小程序不适合承担持续、可靠的语音播报。对可靠性要求较高的场景,更适合使用原生收银 App、常驻收银终端或专用语音设备。
能否继续使用支付宝 App 自带播报
可以先查看商家支付宝 App 中是否有“收钱到账语音提醒”等设置,并确认通知权限、音量和后台运行权限已经开启。不过,即使这些设置都正确,也不能保证所有 JSAPI 在线交易都会触发播报。
如果业务允许改用支付宝收钱码、当面付等线下收款产品,可以使用真实商户账号和设备验证支付宝的原生播报效果。具体是否支持,仍要以当前接入产品的官方说明和实际测试结果为准,不能只根据二维码支付和 JSAPI 支付的表面差异判断。
注意事项
- 应以支付宝服务端异步通知作为支付结果的主要依据,不能只依赖
my.tradePay的前端回调。 - 验签通过后,仍需核对
app_id、收款方、订单号、订单金额和交易状态。 - 私钥只能保存在服务端,不能写入小程序代码。
- 支付通知接口必须使用 HTTPS,同时做好访问日志和异常告警。
- 播报事件必须绑定正确的商户和门店,避免把订单消息推送到错误设备。
- 支付结果确认之前,不能播报“到账”。
- 如果支付宝没有开放对应的原生语音接口,就无法通过 JSAPI、URL Scheme 或前端脚本强制触发支付宝 App 播报。
支付宝负责确认支付结果,业务系统负责将结果可靠地发送到商家端,商家端负责播放语音。这套方案可以覆盖 JSAPI 在线支付,也不依赖支付宝 App 的原生播报策略。
备注:内容仅供参考。
