Flutter desktop 如何集成 payment gateway?
结论
Flutter desktop 可以接入支付网关,但通常无法直接复用仅支持 Android、iOS 或 Web 的 Flutter SDK。更可靠的集成方式是:
- 服务端创建支付订单和 Checkout Session。
- 桌面应用通过系统浏览器打开支付网关的托管收银台。
- 支付网关通过 Webhook 将最终结果通知服务端。
- 桌面应用通过轮询、WebSocket 或回调链接获取支付状态。
部分支付页面可以放进桌面 WebView,但不建议优先采用这种方式。许多网关会限制嵌入式浏览器,银行卡验证、3-D Secure、第三方登录以及跳转银行 App 等流程也可能无法在 WebView 中正常完成。
推荐的集成架构
Flutter desktop
│
│ 请求创建订单
▼
业务服务端 ────────► Payment Gateway API
│ │
│ 返回 checkoutUrl │
▼ │
系统浏览器 ◄───────────────┘
│
│ 用户完成支付
▼
Payment Gateway ──Webhook──► 业务服务端
│
更新订单的真实支付状态
│
Flutter desktop ◄──查询/WebSocket─┘
需要特别注意,桌面客户端显示的跳转结果不能作为支付成功的最终依据。服务端必须验证网关发送的 Webhook,或者主动查询支付网关 API,才能确定订单是否支付成功。
实现步骤
1. 服务端创建支付会话
桌面应用只需将商品、套餐或业务订单编号发送给自己的服务端。支付金额应由服务端根据商品数据计算,不能直接采用客户端传入的金额。
下面是一个与具体支付平台无关的接口结构示例:
POST /api/payments/checkout
Content-Type: application/json
{
"productId": "pro_yearly",
"clientOrderId": "order_20260901_001"
}
服务端调用支付网关后,返回支付页面地址和内部订单号:
{
"orderId": "pay_order_12345",
"checkoutUrl": "https://checkout.example-payments.com/session/abc123"
}
支付网关的 Secret Key、签名密钥和商户私钥必须保存在服务端,不能打包到 Flutter 应用中。
2. 使用系统浏览器打开收银台
可以使用 url_launcher 打开支付网关返回的托管页面:
import 'package:url_launcher/url_launcher.dart';
Future<void> openCheckout(String checkoutUrl) async {
final uri = Uri.parse(checkoutUrl);
final opened = await launchUrl(
uri,
mode: LaunchMode.externalApplication,
);
if (!opened) {
throw StateError('无法打开支付页面');
}
}
在 pubspec.yaml 中添加依赖时,应按照项目当前使用的 Flutter/Dart SDK 选择兼容版本,不要照搬未经确认的版本号:
dependencies:
flutter:
sdk: flutter
url_launcher: any
正式项目可以通过 flutter pub add url_launcher 添加依赖,由 Pub 解析兼容版本:
flutter pub add url_launcher
3. 查询支付结果
打开浏览器后,桌面应用可以定期向业务服务端查询订单状态:
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<String> waitForPayment(String orderId) async {
const maxAttempts = 60;
for (var attempt = 0; attempt < maxAttempts; attempt++) {
final response = await http.get(
Uri.parse('https://api.example.com/payments/$orderId'),
);
if (response.statusCode != 200) {
throw StateError('查询支付状态失败:${response.statusCode}');
}
final data = jsonDecode(response.body) as Map<String, dynamic>;
final status = data['status'] as String;
if (status == 'paid' ||
status == 'failed' ||
status == 'cancelled' ||
status == 'expired') {
return status;
}
await Future<void>.delayed(const Duration(seconds: 2));
}
return 'pending';
}
服务端可以返回类似的数据结构:
{
"orderId": "pay_order_12345",
"status": "paid"
}
实际项目还要加入认证、超时处理、网络重试,以及应用退出后的订单恢复逻辑。服务端也应限制轮询频率,避免客户端产生大量请求。
是否可以使用 WebView
技术上可以,但必须同时满足以下两个条件:
- 所选 Flutter WebView 插件明确支持目标桌面系统。
- 支付网关明确允许其 Checkout 页面在嵌入式 WebView 中运行。
Windows、macOS 和 Linux 的 WebView 实现并不统一。即使支付页面可以打开,仍可能出现以下问题:
- 3-D Secure 验证弹窗或重定向失效。
window.open、Cookie、跨站存储或多窗口流程不兼容。- 银行登录、验证码或身份认证页面拒绝嵌入。
- 网关通过
X-Frame-Options或 CSP 限制嵌入。 - Linux 用户环境中缺少相应的 WebView 运行时。
- 支付平台的风控系统将嵌入式浏览器判定为异常环境。
如果支付平台的官方文档要求使用系统浏览器,就不应通过修改 User-Agent、拦截跳转等方式绕过限制。
WebView 更适合展示普通网页,不适合在未经支付平台允许的情况下承载需要强认证和复杂跨域跳转的支付流程。
将用户带回桌面应用
支付完成后,可以配置这样的返回地址:
myapp://payment/complete?orderId=pay_order_12345
为此,需要分别在 Windows、macOS 和 Linux 上注册自定义 URI Scheme,并处理单实例应用、参数校验和重复唤起等问题。支付网关是否接受自定义 Scheme 作为返回地址,需要查阅对应文档。
兼容性更好的方案是使用 HTTPS 中转页:
https://example.com/payment/complete?orderId=pay_order_12345
中转页可以显示”支付已完成,可以返回应用”,并尝试跳转到自定义 Scheme。即使未能成功唤起桌面应用,应用仍可通过轮询发现订单状态已经变化。
不能根据回调 URL 中的 success=true 直接发放商品。URL 参数可以被伪造,最终状态仍须以服务端验证的支付结果为准。
其他可行方式
如果不想使用浏览器,还可以考虑以下方案:
- 直接调用网关的 REST API:仅适用于支付平台明确允许的流程,敏感凭据和签名操作仍须放在服务端。
- 官方桌面 SDK:如果网关提供 Windows、macOS 或 Linux 原生 SDK,可以通过 Flutter Platform Channels 或 FFI 进行封装。
- 二维码支付:服务端生成支付二维码,由桌面端展示,用户通过手机完成付款,桌面端再查询订单状态。
- 支付链接:服务端生成一次性 Payment Link,然后交给系统浏览器打开。
- WebSocket/SSE:服务端收到 Webhook 后主动通知桌面客户端,从而减少轮询延迟。
封装原生 SDK 的成本通常较高,还要分别处理各桌面平台的构建、签名和发布流程。除非必须连接读卡器、POS 设备或其他本地支付硬件,否则托管收银台通常更容易维护。
安全与合规注意事项
- 不要在 Flutter 桌面应用中保存 Secret Key、商户私钥或 Webhook Secret。
- 不要让客户端决定价格、折扣、币种,也不要由客户端判断订单是否已经支付。
- Webhook 必须验证签名,并妥善处理重复通知和乱序通知。
- 创建订单、确认支付和发放权益都应设计为幂等操作。
- 不要在自己的页面或 WebView 中采集银行卡号,除非已经满足支付平台的要求,并承担相应的 PCI DSS 合规责任。
- 日志中不要记录完整卡号、验证码、访问令牌或包含敏感信息的支付 URL。
- 应妥善处理用户关闭浏览器、付款超时、重复付款、退款和应用重启等情况。
对于普通 Flutter desktop 应用,建议优先采用”服务端创建支付会话 + 系统浏览器打开托管收银台 + Webhook 确认结果”的方案。只有当支付网关明确支持嵌入式页面,并且已经完整验证 Windows、macOS 和 Linux 上的认证与跳转流程时,才考虑使用 WebView。
备注:内容仅供参考。
