Flutter集成Google Tez:如何获取被启动应用的返回数据?
我明白你的困扰——用url_launcher只能启动Tez完成支付,但拿不到交易结果,这确实是因为url_launcher本身只负责启动外部应用,并不处理第三方应用返回的响应数据。你尝试的StreamSubscription监听的只是「是否成功启动应用」的布尔值,不是支付交易的结果,所以自然没用。
下面给你两种可行的解决方案:
方案一:通过Platform Channel自定义实现(Android原生层处理回调)
UPI支付的结果是通过Android的Intent返回的,我们需要在原生Android代码中用ActivityResultContracts(替代旧的startActivityForResult)来接收Tez返回的结果,再通过MethodChannel传递给Flutter。
Flutter端代码修改
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() { runApp(const MaterialApp( title: 'UPI Demo', home: UpiDemo(), )); } class UpiDemo extends StatefulWidget { const UpiDemo({super.key}); @override _UpiDemoState createState() => _UpiDemoState(); } class _UpiDemoState extends State<UpiDemo> { static const platform = MethodChannel('com.example.upi_demo/upi'); String? transactionResult; Future<void> launchTez() async { try { final String result = await platform.invokeMethod('launchTez'); setState(() { transactionResult = result; print('交易结果:$transactionResult'); }); } on PlatformException catch (e) { setState(() { transactionResult = "调用失败: '${e.message}'."; }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('UPI Demo'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: launchTez, child: const Text('Click to Pay Rs.1.0/-'), ), if (transactionResult != null) Padding( padding: const EdgeInsets.all(16.0), child: Text('结果:$transactionResult'), ) ], ), ), ); } }
Android原生代码修改(MainActivity.kt)
package com.example.upi_demo import android.content.Intent import android.net.Uri import androidx.activity.result.contract.ActivityResultContracts import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class MainActivity : FlutterActivity() { private val CHANNEL = "com.example.upi_demo/upi" private var resultCallback: MethodChannel.Result? = null private val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == RESULT_OK) { val data = result.data val response = data?.getStringExtra("response") ?: "无响应数据" resultCallback?.success(response) } else { resultCallback?.error("CANCELLED", "用户取消了交易", null) } } override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "launchTez") { resultCallback = result val payeeAddress = "mobilenumber@upi" val payeeName = "payeename" val transactionNote = "Test for Deeplinking" val amount = "1" val currencyUnit = "INR" val uri = Uri.parse("upi://pay?pa=$payeeAddress&pn=$payeeName&tn=$transactionNote&am=$amount&cu=$currencyUnit") val intent = Intent(Intent.ACTION_VIEW, uri) startForResult.launch(intent) } else { result.notImplemented() } } } }
这段代码的核心是用registerForActivityResult监听Tez返回的Intent,从中提取UPI响应字符串(格式一般是txnId=xxx;responseCode=xxx;...),再通过MethodChannel传给Flutter。
方案二:使用现成的UPI支付插件
如果不想写原生代码,可以用社区维护的UPI插件,比如upi_india(注意检查插件的最新版本和兼容性)。这类插件已经封装了UPI支付的启动和结果回调逻辑,使用起来更简单:
- 添加依赖到
pubspec.yaml:
dependencies: upi_india: ^2.1.0 # 请使用最新版本
- 示例代码:
import 'package:flutter/material.dart'; import 'package:upi_india/upi_india.dart'; void main() { runApp(const MaterialApp(home: UpiDemo())); } class UpiDemo extends StatefulWidget { const UpiDemo({super.key}); @override _UpiDemoState createState() => _UpiDemoState(); } class _UpiDemoState extends State<UpiDemo> { UpiIndia _upiIndia = UpiIndia(); List<UpiApp>? apps; @override void initState() { super.initState(); _upiIndia.getAllUpiApps().then((value) { setState(() { apps = value; }); }); } Future<void> initiateTransaction(UpiApp app) async { UpiResponse response = await _upiIndia.startTransaction( app: app, pa: "mobilenumber@upi", pn: "payeename", tn: "Test for Deeplinking", am: "1", cu: "INR", ); setState(() { print(response.transactionId); print(response.responseCode); // 这里可以处理交易结果 }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('UPI Demo')), body: apps == null ? const Center(child: CircularProgressIndicator()) : ListView.builder( itemCount: apps!.length, itemBuilder: (context, index) { return ListTile( title: Text(apps![index].name), leading: Image.memory(apps![index].icon, height: 40), onTap: () => initiateTransaction(apps![index]), ); }, ), ); } }
这样插件会帮你处理启动应用和接收结果的逻辑,无需自己写原生代码。
为什么你的原有代码无效?
url_launcher的launch方法返回的Future<bool>仅仅表示「是否成功唤起了目标应用」,和支付交易的结果完全无关。你用asStream().listen监听的只是这个布尔值,自然拿不到Tez返回的交易响应数据。只有通过原生层的Intent结果回调,或者封装好的插件,才能获取到UPI支付的最终结果。
内容的提问来源于stack exchange,提问作者Mahi

