Flutter App 支付成功语音播报方案
Flutter App 支付成功语音播报方案:TTS、后台限制和 SignalR 怎么用
一句话结论
这个需求可以做,而且不一定要买外接收款音箱,也不一定要接第三方语音接口。
最适合第一期上线的方案是:
后端确认支付成功
→ 通知 Flutter App
→ Flutter App 用本地 TTS 播报
→ 播报“付款成功,到账 XX 元”
这里真正难的不是“怎么让手机说话”,而是“付款成功这件事怎么实时、可靠地传到 App”。
需求背景
门店希望会员付款后,前台设备能立刻播报收款结果,比如:
付款成功,到账 99 元
覆盖场景包括:
会员扫码购买商品
会员扫码办卡、续费、储值
会员扫码购买私教课、团课
小程序线上支付
后台/收银台生成收款码后会员扫码付款
对应的播报文案可以先定三类:
支付成功:付款成功,到账 XX 元
支付超时:支付超时,请重新扫码付款
支付失败:付款失败,请检查支付账户后重试
同时,财务中心继续展示支付记录明细,比如支付时间、金额、支付方式、业务类型、会员信息、订单号等。
Flutter 语音播报本身怎么做
Flutter 端可以使用本地 TTS,也就是 Text To Speech,文字转语音。
你可以把它理解为:
App 把“付款成功,到账 99 元”这句话交给系统
系统用手机自己的语音能力读出来
它不是录音,所以不需要麦克风权限。
它不是第三方语音合成接口,所以没有额外接口费用。
它一般也不是读取本地音乐文件,所以不需要媒体文件读取权限。
项目里如果已经引入了 flutter_tts,可以封装一个支付播报服务:
import 'package:flutter_tts/flutter_tts.dart';
class PaymentVoiceService {
static final FlutterTts _tts = FlutterTts();
static bool _inited = false;
static Future<void> init() async {
if (_inited) {
return;
}
await _tts.setLanguage('zh-CN');
await _tts.setVolume(1.0);
await _tts.setSpeechRate(0.5);
await _tts.setPitch(1.0);
_inited = true;
}
static Future<void> speakSuccess(String amount) async {
await speak('付款成功,到账$amount元');
}
static Future<void> speakTimeout() async {
await speak('支付超时,请重新扫码付款');
}
static Future<void> speakFailed() async {
await speak('付款失败,请检查支付账户后重试');
}
static Future<void> speak(String text) async {
await init();
await _tts.stop();
await _tts.speak(text);
}
}
这个服务后面可以被“收款页轮询结果”“SignalR 收到支付成功消息”“晨报播报”等多个地方复用。
支付结果怎么通知 App
这块是核心。
会员付款成功后,通常是支付平台先通知后端。比如微信、支付宝、汇付等支付平台,会把支付结果回调给服务端。
所以完整链路应该是:
会员扫码付款
→ 支付平台确认支付成功
→ 支付平台回调后端
→ 后端更新订单状态、写财务流水
→ 后端通知 App
→ App 本地 TTS 播报
前端不能凭空知道会员已经付款了。前端必须通过某种方式拿到后端的支付结果。
常见有三种做法。
方案一:轮询订单状态
这是第一期最容易落地的方式。
流程是:
App 生成收款订单
→ App 展示二维码
→ App 每隔 2 秒查一次订单状态
→ 查到成功后播报
→ 超过 2 到 5 分钟未支付,就提示支付超时
优点:
实现简单
不需要长连接
适合当前 App 停留在收款二维码页面的场景
缺点:
只能监听当前订单
如果会员在小程序里自己付款,App 不一定知道该查哪笔订单
App 切后台后,定时器可能不稳定
所以轮询适合做第一期,但不是完整终局方案。
方案二:SignalR 实时通知
你前面说的 “singler” 应该是 SignalR。
SignalR 是微软 .NET 生态里常用的实时通信框架。它可以让服务端主动通知客户端。
大白话说:
不是 App 一直问后端“付了吗?付了吗?”
而是后端一旦知道“付了”,马上告诉 App
SignalR 底层会优先使用 WebSocket;如果不支持,会降级到 Server-Sent Events;再不行会用 Long Polling。业务层不用太关心底层到底是哪一种。
支付播报里可以这样用:
App 登录后连接 SignalR
→ App 告诉后端自己属于哪个门店
→ 后端把这个连接加入门店分组
→ 某门店有支付成功
→ 后端向这个门店分组推送 paymentSuccess
→ App 收到消息后播报
服务端推给 App 的消息可以约定成这样:
{
"event": "paymentSuccess",
"orderId": "202606180001",
"branchId": "10001",
"amount": "99.00",
"payType": "微信支付",
"businessType": "商品消费",
"payTime": "2026-06-18 10:30:00"
}
App 收到后做三件事:
判断是不是当前门店的订单
判断 orderId 有没有播报过
调用 TTS 播报“付款成功,到账 99 元”
Flutter 前端如何接 SignalR
Flutter 没有官方的一等公民 SignalR 客户端,一般会使用社区包。比较常见的是:
signalr_netcore
signalr_core
如果你们后端是 ASP.NET Core SignalR,可以先评估 signalr_netcore,它的包说明里写到支持 WebSocket、Server-Sent Events、Long Polling,并支持自动重连。
依赖示例:
dependencies:
signalr_netcore:
封装一个支付 SignalR 服务:
import 'dart:async';
import 'package:signalr_netcore/signalr_client.dart';
typedef PaymentSuccessCallback = Future<void> Function(
Map<String, dynamic> data,
);
class PaymentSignalRService {
HubConnection? _connection;
bool _starting = false;
Future<void> start({
required String hubUrl,
required String token,
required PaymentSuccessCallback onPaymentSuccess,
}) async {
if (_starting) {
return;
}
if (_connection?.state == HubConnectionState.Connected) {
return;
}
_starting = true;
final httpOptions = HttpConnectionOptions(
accessTokenFactory: () async => token,
);
final connection = HubConnectionBuilder()
.withUrl(hubUrl, options: httpOptions)
.withAutomaticReconnect(
retryDelays: [0, 2000, 5000, 10000, 30000, null],
)
.build();
connection.on('paymentSuccess', (arguments) async {
final first = arguments?.isNotEmpty == true ? arguments!.first : null;
if (first is Map) {
await onPaymentSuccess(Map<String, dynamic>.from(first));
}
});
connection.onclose(({error}) {
_connection = null;
});
_connection = connection;
try {
await connection.start();
} finally {
_starting = false;
}
}
Future<void> stop() async {
final connection = _connection;
_connection = null;
await connection?.stop();
}
}
业务层使用时大概这样:
final paymentSignalRService = PaymentSignalRService();
Future<void> startPaymentListener() async {
await paymentSignalRService.start(
hubUrl: 'https://你的域名/paymentHub',
token: '用户登录 token',
onPaymentSuccess: (data) async {
final orderId = data['orderId']?.toString() ?? '';
final amount = data['amount']?.toString() ?? '0';
if (orderId.isEmpty) {
return;
}
final hasSpoken = await PaymentBroadcastDedup.hasSpoken(orderId);
if (hasSpoken) {
return;
}
await PaymentBroadcastDedup.markSpoken(orderId);
await PaymentVoiceService.speakSuccess(amount);
},
);
}
这里还需要一个去重工具,避免同一笔订单重复播报:
class PaymentBroadcastDedup {
static final Set<String> _spokenOrderIds = <String>{};
static Future<bool> hasSpoken(String orderId) async {
return _spokenOrderIds.contains(orderId);
}
static Future<void> markSpoken(String orderId) async {
_spokenOrderIds.add(orderId);
}
}
正式项目里不要只放内存,建议把最近播报过的订单号存到 SharedPreferences 或项目已有的本地存储里。否则 App 重启后,短时间内同一笔订单可能又被播一次。
SignalR 解决什么,不解决什么
SignalR 解决的是:
服务端怎么实时通知客户端
它不解决的是:
App 被系统挂起后还能不能一直运行
App 被用户杀掉后还能不能收到消息
iOS 后台能不能直接 TTS 播报
所以它不是后台保活技术。
可以这样记:
App 前台:SignalR 很适合,实时性好
App 后台:SignalR 不一定稳定,取决于系统限制
App 被杀:SignalR 收不到,只能靠系统推送
后台播报能不能做
要分平台。
Android 和鸿蒙相对可做,但最好不要只依赖 Flutter 层的定时器或普通长连接。更稳的是做“前台服务”:
用户开启收款语音播报
→ App 启动一个常驻前台服务
→ 状态栏显示“正在监听收款播报”
→ 收到支付消息后调用 TTS 播报
这通常会涉及:
Android 13+ 通知权限 POST_NOTIFICATIONS
前台服务 Service 配置
部分国产机型的后台运行/自启动/电池优化设置引导
iOS 更严格。App 进入后台后,系统通常会挂起 App,不能承诺一直保持 SignalR 连接,也不建议承诺后台直接 TTS 播报。
iOS 更稳的产品设计是:
支付成功后发通知
→ 用户点通知回到 App
→ App 自动播报详情
是否需要应用市场开通能力
如果只是 App 前台付款语音播报:
不需要应用市场额外开通能力
不需要麦克风权限
不需要录音权限
不需要第三方语音接口
如果是 App 打开时播报每日晨报:
也不需要额外市场能力
如果要做后台提醒或推送:
Android 13+ 需要通知权限
iOS 需要通知授权和 APNs 推送能力
华为/鸿蒙如果用华为推送,需要在 AppGallery Connect / HMS 开通 Push Kit
小米、OPPO、vivo 等厂商推送,也要各自平台配置
如果要做 Android 后台持续监听并播报:
需要前台服务配置
需要通知栏常驻提示
可能需要用户允许后台运行
APNs 和厂商推送到底是什么
APNs 全称是 Apple Push Notification service,也就是苹果官方推送服务。
大白话说:
APNs 就是 iPhone 上 App 收通知必须走的苹果官方通道
为什么需要它?
因为 iOS 不允许普通 App 在后台一直开着网络连接等消息。App 切到后台一段时间后,系统可能会把它挂起。这个时候 SignalR、WebSocket、SSE 这些长连接都不一定还活着。
但是苹果自己的 APNs 通道是系统级能力。即使 App 没打开,系统也可以收到苹果服务器下发的通知,然后展示给用户。
链路大概是:
后端发现支付成功
→ 后端请求苹果 APNs
→ 苹果 APNs 把通知发到用户 iPhone
→ iPhone 系统弹出通知
→ 用户点击通知打开 App
→ App 再播报详情
所以 APNs 不是一个 Flutter 插件,也不是一段前端代码能单独完成的东西。它需要后端、iOS 证书、Apple Developer 后台一起配合。
iOS 侧通常要做这些配置:
Apple Developer 后台打开 Push Notifications 能力
Xcode 里给 Runner 打开 Push Notifications capability
如果需要后台通知处理,再打开对应 Background Modes
App 首次使用通知时,向用户申请通知权限
后端保存设备 token
后端用 APNs key 或证书向苹果推送通知
用户看到的权限弹窗大概就是:
“菲特云”想给您发送通知
用户允许后,App 才能正常弹通知。
Android 这边也有类似概念,只是更碎。
国内 Android 手机有很多厂商,比如华为、小米、OPPO、vivo、荣耀等。它们都有自己的推送通道。为什么不用一个统一的就好了?因为国内很多手机对后台进程限制很严,App 被杀掉后,普通网络连接基本就断了。要想 App 没打开也能收到通知,通常要接厂商推送。
常见厂商配置大概是:
华为/鸿蒙:AppGallery Connect / HMS Push Kit
小米:小米开放平台推送
OPPO:OPPO PUSH
vivo:vivo 推送
荣耀:荣耀推送
Android 13 以后,即使接了推送,也还需要用户授权通知权限:
POST_NOTIFICATIONS
所以厂商推送可以理解为:
App 不在前台时,系统或厂商帮你把通知送到手机
它适合做:
支付成功通知
每日晨报提醒
活动提醒
待办提醒
但它不太适合直接等同于“后台语音播报”。尤其是 iOS,推送通常负责把用户叫回来,真正的详细语音播报建议在用户打开 App 后执行。
每日晨报怎么设计更稳
每日晨报可以分两种。
第一种:用户打开 App 后播报。
早上用户打开 App
→ App 请求昨日/今日营收数据
→ TTS 播报:
早上好,昨日门店营收 12345 元,新增会员 8 人
这个方案简单、稳定、权限少。
第二种:App 没打开也要早上 8 点自动响。
这个就复杂很多。
Android 可以通过通知、定时任务、前台服务尽量实现,但受系统和厂商限制。
iOS 不建议做成后台强制语音播报,更建议做通知提醒:
今日晨报已生成,点击查看
用户点进 App 后再播报详细内容。
推荐落地路线
第一期建议这样做:
1. 封装 PaymentVoiceService,统一负责 TTS 播报
2. 收款二维码页面先做订单状态轮询
3. 支付成功、失败、超时分别播报对应文案
4. 加订单号去重,避免重复播报
5. 财务中心展示支付流水详情
第二期再增强实时性:
1. 后端提供 SignalR paymentHub
2. App 登录后连接 SignalR
3. 后端按门店分组推送 paymentSuccess
4. App 收到消息后播报
5. 增加“最近未播报支付记录”补偿接口,防止漏单
第三期处理后台体验:
Android/鸿蒙:评估前台服务 + 通知权限 + 后台运行引导
iOS:走 APNs 通知,点击通知回 App 后播报
为什么还需要补偿接口
长连接不是百分百可靠。
网络会断,App 会切后台,系统会限制连接,服务端也可能重启。
所以推荐做双保险:
SignalR:负责实时播报
补偿接口:负责防漏单
补偿接口可以长这样:
GET /payment/unbroadcasted?branchId=xxx&lastTime=xxx
返回最近几分钟当前门店已成功但 App 可能没播报过的订单。
App 拿到后仍然按 orderId 去重:
没播过 → 播报
播过 → 跳过
财务中心需要展示什么
财务中心不是触发播报的核心,但它要承接支付结果,方便对账。
建议支付记录里展示:
支付时间
支付金额
支付方式:微信、支付宝、汇付、储值卡等
业务类型:商品消费、办卡、续费、储值、私教课、团课
会员姓名和手机号
订单号
支付状态:成功、失败、超时、退款
收款门店
操作来源:小程序、收款码、后台开单
备注
这样后面排查“有没有播报”“有没有到账”“是不是同一笔订单”会更方便。
测试重点
测试时不要只测“能不能说话”,还要测这些情况:
同一笔订单是否只播报一次
连续多笔支付是否按顺序播报
手机静音/媒体音量为 0 时表现如何
中文 TTS 不可用时是否有提示
App 前台时 SignalR 是否能实时收到
App 切后台后 Android 是否还能收到
iOS 切后台后是否改走通知
网络断开重连后是否能继续收到消息
SignalR 漏消息后补偿接口是否能补播
财务中心记录是否和播报金额一致
最后总结
这个需求可以拆成三层看:
语音层:Flutter 本地 TTS,负责把文字读出来
消息层:轮询或 SignalR,负责把支付结果送到 App
系统层:前台、后台、推送、权限,决定 App 能不能持续运行
最现实的上线方案是:
前台收款播报:TTS + 订单状态轮询
实时增强:SignalR 推送支付成功
防漏单:最近支付成功记录补偿
后台体验:Android/鸿蒙评估前台服务,iOS 使用通知唤醒
每日晨报:优先做 App 打开后自动播报
这样做既能快速上线,又不会一开始就被后台保活、厂商推送、iOS 审核这些复杂问题拖住。
参考资料
- Microsoft SignalR 介绍:https://learn.microsoft.com/en-us/aspnet/core/signalr/introduction
- Microsoft SignalR JavaScript 客户端:https://learn.microsoft.com/en-us/aspnet/core/signalr/javascript-client
- signalr_netcore Flutter 包:https://pub.dev/packages/signalr_netcore
- signalr_core Dart 包:https://pub.dev/packages/signalr_core
- Android 通知权限:https://developer.android.com/develop/ui/views/notifications/notification-permission
- Android 前台服务:https://developer.android.com/develop/background-work/services/fgs
- Apple 后台音频说明:https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/MediaPlaybackGuide/Contents/Resources/en.lproj/ConfiguringAudioSettings/ConfiguringAudioSettings.html
本文来自博客园,作者:jialiangzai,转载请注明原文链接:https://www.cnblogs.com/zsnhweb/p/20635896

浙公网安备 33010602011771号