第一步安装flutter pub add tobias
如果你需要为 鸿蒙(OpenHarmony) 平台进行适配,或者需要解决某些特定的原生依赖冲突,可能需要以 Git 的形式引入特定的分支或版本。例如:
XML
# 在 pubspec.yaml 中手动配置(以鸿蒙适配版为例)
dependencies:
tobias:
git:
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
path: "packages/fluttertpc_tobias"
配置 Android 端 (AndroidManifest.xml)
ndroid 端需要配置网络权限,并添加一个 URL Scheme 过滤器,确保支付宝支付完成后能正确跳回你的 App。
- 打开你的 Flutter 项目中的
android/app/src/main/AndroidManifest.xml文件。 - 在
<manifest>标签内(通常在<application>标签上方)添加网络权限。 - 找到你的主 Activity(通常包含
android.intent.action.MAIN的那个<activity>标签),在其内部的<intent-filter>中添加支付宝的跳转配置。
XML
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="你的包名">
<!-- 1. 添加网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:label="hm_shop"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- 2. 添加支付宝回调的 intent-filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- 这里的 scheme 必须和 iOS Info.plist 里配置的完全一致 -->
<data android:scheme="alipay你的APP_ID" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
Flutter 核心支付代码 (tobias 调用)
Dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:fluwx/fluwx.dart';
import 'package:tobias/tobias.dart'; // 1. 引入 tobias 支付宝插件
import 'package:dio/dio.dart';
class OrderPayPage extends StatefulWidget {
const OrderPayPage({super.key});
@override
State<OrderPayPage> createState() => _OrderPayPageState();
}
class _OrderPayPageState extends State<OrderPayPage> {
bool _isPaying = false;
FluwxCancelable? _subscription;
final Fluwx _fluwx = Fluwx();
final Tobias _tobias = Tobias(); // 2. 实例化 Tobias
final Dio _dio = Dio();
// 3. 增加支付方式状态,默认为 wechat
String _payMethod = 'wechat'; // 'wechat' 或 'alipay'
final Map<String, dynamic> _order = {
"orderNo": "HM20260528001",
"goodsName": "小米手机14 256GB",
"price": 3999.00,
"count": 1,
"total": 3999.00,
"address": "河南省郑州市金水区XX街道XX小区",
"receiver": "张三",
"phone": "138****8888"
};
@override
void initState() {
super.initState();
_initFluwx();
_subscription = _fluwx.addSubscriber((response) {
if (response is WeChatPaymentResponse) {
setState(() => _isPaying = false);
if (response.isSuccessful) {
_showMsg("✅ 微信支付成功!");
} else if (response.errCode == -2) {
_showMsg("🛑 您已取消微信支付");
} else {
_showMsg("❌ 微信支付失败:${response.errCode}");
}
}
});
}
Future<void> _initFluwx() async {
await _fluwx.registerApi(
appId: "你的微信AppID",
universalLink: "你的Universal Link",
);
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
// 向后端请求支付参数(根据支付方式请求不同的接口)
Future<Map<String, dynamic>> _getPayParams() async {
// 根据 _payMethod 请求不同的后端接口
final url = _payMethod == 'wechat'
? "https://你的后端域名/api/wxpay/create"
: "https://你的后端域名/api/alipay/create"; // 假设的支付宝后端接口
try {
final response = await _dio.post(
url,
data: {
"orderNo": _order["orderNo"],
"totalFee": _order["total"],
"body": _order["goodsName"]
},
options: Options(headers: {"Content-Type": "application/json"}),
);
if (response.statusCode == 200) {
return response.data;
} else {
throw Exception("获取支付参数失败");
}
} catch (e) {
throw Exception("网络请求异常: $e");
}
}
// 4. 统一下单支付入口
Future<void> _pay() async {
if (_isPaying) return;
setState(() => _isPaying = true);
try {
if (_payMethod == 'wechat') {
await _doWechatPay();
} else {
await _doAlipayPay();
}
} catch (e) {
_showMsg("支付异常:$e");
setState(() => _isPaying = false);
}
}
// 微信支付核心逻辑
Future<void> _doWechatPay() async {
final isInstalled = await _fluwx.isWeChatInstalled;
if (!isInstalled) {
_showMsg("请先安装微信客户端");
setState(() => _isPaying = false);
return;
}
final payParams = await _getPayParams();
await _fluwx.pay(
which: Payment(
appId: payParams['appId'].toString(),
partnerId: payParams['partnerId'].toString(),
prepayId: payParams['prepayId'].toString(),
packageValue: payParams['package'].toString(),
nonceStr: payParams['nonceStr'].toString(),
timestamp: int.parse(payParams['timeStamp'].toString()),
sign: payParams['sign'].toString(),
),
);
// 微信支付的结果在 addSubscriber 监听中处理,这里不立即关闭 loading
}
// 5. 支付宝支付核心逻辑
Future<void> _doAlipayPay() async {
final isInstalled = await _tobias.isAliPayInstalled;
if (!isInstalled) {
_showMsg("请先安装支付宝客户端");
setState(() => _isPaying = false);
return;
}
final payParams = await _getPayParams();
// 支付宝后端返回的通常是一个完整的签名字符串 (orderString)
final String orderString = payParams['orderString'].toString();
final result = await _tobias.pay(orderString);
setState(() => _isPaying = false);
if (result['resultStatus'] == "9000") {
_showMsg("✅ 支付宝支付成功!");
} else if (result['resultStatus'] == "6001") {
_showMsg("🛑 您已取消支付宝支付");
} else {
_showMsg("❌ 支付宝支付失败:${result['memo']}");
}
}
void _showMsg(String msg) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("订单详情")),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildAddressCard(),
const SizedBox(height: 12),
_buildGoodsCard(),
const SizedBox(height: 12),
_buildTotalCard(),
],
),
),
// 6. 修改底部支付栏,增加支付方式切换
bottomNavigationBar: _buildPayBar(),
);
}
// ... (省略 _buildAddressCard, _buildGoodsCard, _buildTotalCard, _buildRow, _cardDecoration,保持你原来的代码不变) ...
Widget _buildAddressCard() {
return Container(
padding: const EdgeInsets.all(16),
decoration: _cardDecoration(),
child: Row(
children: [
const Icon(Icons.location_on, color: Colors.red),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("${_order["receiver"]} ${_order["phone"]}",
style: const TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(_order["address"], style: TextStyle(color: Colors.grey[600])),
],
),
)
],
),
);
}
Widget _buildGoodsCard() {
return Container(
padding: const EdgeInsets.all(16),
decoration: _cardDecoration(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("订单号:${_order["orderNo"]}", style: const TextStyle(color: Colors.grey)),
const SizedBox(height: 8),
Row(
children: [
Container(
width: 80,
height: 80,
color: Colors.grey[200],
child: const Icon(Icons.phone_android, size: 40),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_order["goodsName"], style: const TextStyle(fontSize: 15)),
const SizedBox(height: 4),
Text("¥${_order["price"]}",
style: const TextStyle(color: Colors.red, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text("x${_order["count"]}", style: TextStyle(color: Colors.grey[600])),
],
),
)
],
)
],
),
);
}
Widget _buildTotalCard() {
return Container(
padding: const EdgeInsets.all(16),
decoration: _cardDecoration(),
child: Column(
children: [
_buildRow("商品总价", "¥${_order["price"]}"),
const SizedBox(height: 8),
_buildRow("实付金额", "¥${_order["total"]}", isTotal: true),
],
),
);
}
// 7. 重构底部栏,增加支付方式单选
Widget _buildPayBar() {
return Container(
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.white,
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 4)],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 支付方式切换行
Row(
children: [
const Text("选择支付方式:", style: TextStyle(color: Colors.grey)),
const SizedBox(width: 16),
_buildPayMethodRadio("微信支付", "wechat", const Color(0xFF07C160)),
const SizedBox(width: 16),
_buildPayMethodRadio("支付宝", "alipay", const Color(0xFF1677FF)),
],
),
const SizedBox(height: 12),
// 支付按钮行
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("实付:¥${_order["total"]}",
style: const TextStyle(color: Colors.red, fontSize: 18, fontWeight: FontWeight.bold)),
ElevatedButton(
onPressed: _isPaying ? null : _pay,
style: ElevatedButton.styleFrom(
backgroundColor: _payMethod == 'wechat' ? const Color(0xFF07C160) : const Color(0xFF1677FF),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
child: _isPaying
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2))
: Text(_payMethod == 'wechat' ? "微信支付" : "支付宝支付", style: const TextStyle(color: Colors.white)),
)
],
),
],
),
);
}
// 8. 支付方式单选按钮组件
Widget _buildPayMethodRadio(String title, String value, Color color) {
return GestureDetector(
onTap: () => setState(() => _payMethod = value),
child: Row(
children: [
Container(
width: 18,
height: 18,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: _payMethod == value ? color : Colors.grey),
color: _payMethod == value ? color : Colors.transparent,
),
child: _payMethod == value
? const Center(child: Icon(Icons.check, size: 14, color: Colors.white))
: null,
),
const SizedBox(width: 6),
Text(title, style: TextStyle(color: _payMethod == value ? color : Colors.black)),
],
),
);
}
Widget _buildRow(String label, String value, {bool isTotal = false}) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(color: isTotal ? Colors.black : Colors.grey[600])),
Text(value,
style: TextStyle(
color: isTotal ? Colors.red : Colors.black,
fontWeight: isTotal ? FontWeight.bold : FontWeight.normal)),
],
);
}
BoxDecoration _cardDecoration() {
return BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 2)],
);
}
}
Android 打包避坑(Release 模式闪退)
这是新手最容易踩的坑!Android 在打包正式 Release 版本时,默认会开启代码混淆(R8),这会导致支付宝 SDK 的核心类被误删,从而引发 ClassNotFoundException 导致 App 闪退。
解决方法:
在 android/app/proguard-rules.pro 文件中(如果没有该文件可以手动新建一个),添加以下防混淆规则
XML
# 保留支付宝 SDK 所有类不被混淆
-keep class com.alipay.sdk.** { *; }
-keep class com.alipay.android.phone.msp.auth.** { *; }
-keep class com.alipay.mobile.** { *; }
-keep class com.alipay.sdk.app.PayTask { *; }
-keep class com.alipay.sdk.app.AuthTask { *; }