flutter实现支付宝支付

第一步安装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。

  1. 打开你的 Flutter 项目中的 android/app/src/main/AndroidManifest.xml 文件。
  2. <manifest> 标签内(通常在 <application> 标签上方)添加网络权限。
  3. 找到你的主 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 { *; }
相关推荐
●VON13 小时前
鸿蒙Flutter实战:待办事项三态筛选器
flutter·华为·harmonyos·鸿蒙
李宏伟~14 小时前
flutter实现直播推流端
flutter
●VON14 小时前
鸿蒙Flutter实战:多选批量删除模式的实现
flutter·华为·harmonyos·鸿蒙
坚果的博客14 小时前
Flutter 三方库(Flutter-New-Badge)适配开源鸿蒙教程
flutter·开源·harmonyos
二蛋和他的大花15 小时前
高德地图 Flutter 插件:跨 Android / iOS / HarmonyOS 的完整实现
android·flutter·ios
不爱吃糖的程序媛15 小时前
鸿蒙Flutter 三方库 country_codes 的 适配实战
flutter·华为·harmonyos
●VON15 小时前
鸿蒙Flutter实战:Markdown编辑与预览实时切换
flutter·华为·harmonyos·鸿蒙
程序员老刘·1 天前
Flutter 3.44 更新要点:很重要但暂时先别升级
flutter·ai编程·跨平台开发·客户端开发
用户86284129549442 天前
Flutter rxflare 计算属性 computed:自动依赖追踪 + 缓存(超实用)
flutter