Flutter统一支付接口插件upi_pay的使用
Flutter统一支付接口插件upi_pay的使用
插件简介
upi_pay
是一个用于在Flutter应用中集成UPI(统一支付接口)支付功能的插件。它允许用户找到手机上已安装的UPI支付应用程序,并通过其中任何一个进行付款。该插件实现了UPI Deep Linking And Proximity Integration Specification。
版本信息
平台支持
平台 | 状态 |
---|---|
Android | |
iOS |
快速开始
依赖添加
在您的 pubspec.yaml
文件中添加 upi_pay
作为依赖项:
dependencies:
...
upi_pay: ^1.1.0
导入包
在需要使用的Dart文件中导入 upi_pay
包:
import 'package:upi_pay/upi_pay.dart';
iOS配置
在 Runner/Info.plist
文件中添加或修改 LSApplicationQueriesSchemes
键,以包含以下自定义查询方案:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>freecharge</string>
<string>gpay</string>
<string>in.fampay.app</string>
<string>lotza</string>
<string>mobikwik</string>
<string>paytm</string>
<string>phonepe</string>
<string>upi</string>
<string>upibillpay</string>
<string>whatsapp</string>
</array>
使用方法
创建插件实例
final upiPay = UpiPay();
获取已安装的应用列表
final List<ApplicationMeta> appMetaList = await upiPay.getInstalledUpiApps();
显示应用详情
Widget appWidget(ApplicationMeta appMeta) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
appMeta.iconImage(48), // Logo
Container(
margin: EdgeInsets.only(top: 4),
alignment: Alignment.center,
child: Text(
appMeta.upiApplication.getAppName(),
textAlign: TextAlign.center,
),
),
],
);
}
发起UPI交易
Future doUpiTransaction(ApplicationMeta appMeta) async {
final UpiTransactionResponse response = await upiPay.initiateTransaction(
amount: '100.00',
app: appMeta.application,
receiverName: 'John Doe',
receiverUpiAddress: 'john@doe',
transactionRef: 'UPITXREF0001',
transactionNote: 'A UPI Transaction',
);
print(response.status);
}
行为、限制和措施
Android平台行为
- 流程:在Android平台上,UPI深度链接规范是通过Intents实现的。发起支付请求时会指定要调用的具体UPI应用。处理完UPI交易后,目标应用会按照规范格式返回响应给插件层,插件层解析此响应并生成
UpiTransactionResponse
对象。 - 措施:建议在客户端之外实现服务器端支付验证,以防客户端上的UPI交易流程被篡改。
iOS平台行为
- 流程:iOS上通过自定义scheme实现UPI深度链接规范。由于无法指定目标应用ID,因此不确定哪个UPI应用会被调用。此外,由于自定义scheme机制限制,无法获取交易状态。
- 措施:需要额外实现支付验证逻辑;并且区分检测到的应用和支持但未检测到的应用。
动态变化
UPI标准和技术不断发展,导致不同UPI应用的行为和功能也在变化。具体参见 Apps 获取各应用当前的功能状态。
示例代码
下面是一个完整的示例项目结构:
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:upi_pay/upi_pay.dart';
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('UPI Pay'),
),
body: Screen(),
),
);
}
}
class Screen extends StatefulWidget {
@override
_ScreenState createState() => _ScreenState();
}
class _ScreenState extends State<Screen> {
String? _upiAddrError;
final _upiAddressController = TextEditingController();
final _amountController = TextEditingController();
final _upiPayPlugin = UpiPay();
bool _isUpiEditable = false;
List<ApplicationMeta>? _apps;
@override
void initState() {
super.initState();
_amountController.text =
(Random.secure().nextDouble() * 10).toStringAsFixed(2);
Future.delayed(Duration(milliseconds: 0), () async {
_apps = await _upiPayPlugin.getInstalledUpiApplications(
statusType: UpiApplicationDiscoveryAppStatusType.all);
setState(() {});
});
}
@override
void dispose() {
_amountController.dispose();
_upiAddressController.dispose();
super.dispose();
}
void _generateAmount() {
setState(() {
_amountController.text = '10';
});
}
Future<void> _onTap(ApplicationMeta app) async {
final err = _validateUpiAddress(_upiAddressController.text);
if (err != null) {
setState(() {
_upiAddrError = err;
});
return;
}
setState(() {
_upiAddrError = null;
});
final transactionRef = Random.secure().nextInt(1 << 32).toString();
print("Starting transaction with id $transactionRef");
final a = await _upiPayPlugin.initiateTransaction(
amount: _amountController.text,
app: app.upiApplication,
receiverName: 'Sharad',
receiverUpiAddress: _upiAddressController.text,
transactionRef: transactionRef,
transactionNote: 'UPI Payment',
// merchantCode: '7372',
);
print(a);
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 16),
child: ListView(
children: <Widget>[
_vpa(),
if (_upiAddrError != null) _vpaError(),
_amount(),
if (Platform.isIOS) _submitButton(),
Platform.isAndroid ? _androidApps() : _iosApps(),
],
),
);
}
Widget _vpa() {
return Container(
margin: EdgeInsets.only(top: 32),
child: Row(
children: <Widget>[
Expanded(
child: TextFormField(
controller: _upiAddressController,
enabled: _isUpiEditable,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'address@upi',
labelText: 'Receiving UPI Address',
),
),
),
Container(
margin: EdgeInsets.only(left: 8),
child: IconButton(
icon: Icon(
_isUpiEditable ? Icons.check : Icons.edit,
),
onPressed: () {
setState(() {
_isUpiEditable = !_isUpiEditable;
});
},
),
),
],
),
);
}
Widget _vpaError() {
return Container(
margin: EdgeInsets.only(top: 4, left: 12),
child: Text(
_upiAddrError!,
style: TextStyle(color: Colors.red),
),
);
}
Widget _amount() {
return Container(
margin: EdgeInsets.only(top: 32),
child: Row(
children: <Widget>[
Expanded(
child: TextField(
controller: _amountController,
readOnly: true,
enabled: false,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Amount',
),
),
),
Container(
margin: EdgeInsets.only(left: 8),
child: IconButton(
icon: Icon(Icons.loop),
onPressed: _generateAmount,
),
),
],
),
);
}
Widget _submitButton() {
return Container(
margin: EdgeInsets.only(top: 32),
child: Row(
children: <Widget>[
Expanded(
child: MaterialButton(
onPressed: () async => await _onTap(_apps![0]),
child: Text('Initiate Transaction',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Colors.white)),
color: Theme.of(context).primaryColor,
height: 48,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6)),
),
),
],
),
);
}
Widget _androidApps() {
return Container(
margin: EdgeInsets.only(top: 32, bottom: 32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
margin: EdgeInsets.only(bottom: 12),
child: Text(
'Pay Using',
style: Theme.of(context).textTheme.bodyMedium,
),
),
if (_apps != null) _appsGrid(_apps!.map((e) => e).toList()),
],
),
);
}
Widget _iosApps() {
return Container(
margin: EdgeInsets.only(top: 32, bottom: 32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
margin: EdgeInsets.only(bottom: 24),
child: Text(
'One of these will be invoked automatically by your phone to '
'make a payment',
style: Theme.of(context).textTheme.bodySmall,
),
),
Container(
margin: EdgeInsets.only(bottom: 12),
child: Text(
'Detected Installed Apps',
style: Theme.of(context).textTheme.bodyMedium,
),
),
if (_apps != null) _discoverableAppsGrid(),
Container(
margin: EdgeInsets.only(top: 12, bottom: 12),
child: Text(
'Other Supported Apps (Cannot detect)',
style: Theme.of(context).textTheme.bodyMedium,
),
),
if (_apps != null) _nonDiscoverableAppsGrid(),
],
),
);
}
GridView _discoverableAppsGrid() {
List<ApplicationMeta> metaList = [];
_apps!.forEach((e) {
if (e.upiApplication.discoveryCustomScheme != null) {
metaList.add(e);
}
});
return _appsGrid(metaList);
}
GridView _nonDiscoverableAppsGrid() {
List<ApplicationMeta> metaList = [];
_apps!.forEach((e) {
if (e.upiApplication.discoveryCustomScheme == null) {
metaList.add(e);
}
});
return _appsGrid(metaList);
}
GridView _appsGrid(List<ApplicationMeta> apps) {
apps.sort((a, b) => a.upiApplication
.getAppName()
.toLowerCase()
.compareTo(b.upiApplication.getAppName().toLowerCase()));
return GridView.count(
crossAxisCount: 4,
shrinkWrap: true,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
physics: NeverScrollableScrollPhysics(),
children: apps
.map(
(it) => Material(
key: ObjectKey(it.upiApplication),
child: InkWell(
onTap: Platform.isAndroid ? () async => await _onTap(it) : null,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
it.iconImage(48),
Container(
margin: EdgeInsets.only(top: 4),
alignment: Alignment.center,
child: Text(
it.upiApplication.getAppName(),
textAlign: TextAlign.center,
),
),
],
),
),
),
)
.toList(),
);
}
}
String? _validateUpiAddress(String value) {
if (value.isEmpty) {
return 'UPI VPA is required.';
}
if (value.split('@').length != 2) {
return 'Invalid UPI VPA';
}
return null;
}
以上代码展示了如何创建一个简单的Flutter应用来演示 upi_pay
插件的基本功能。希望这些信息能帮助您快速上手并成功集成UPI支付功能到您的Flutter项目中。
更多关于Flutter统一支付接口插件upi_pay的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter统一支付接口插件upi_pay的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter项目中使用upi_pay
插件来实现统一支付接口的示例代码。这个插件假设已经支持了多种支付渠道,并且已经集成到你的Flutter项目中。
首先,确保你已经在pubspec.yaml
文件中添加了upi_pay
依赖:
dependencies:
flutter:
sdk: flutter
upi_pay: ^x.y.z # 请替换为实际的版本号
然后运行flutter pub get
来安装依赖。
接下来,你需要在你的Flutter项目中初始化并使用upi_pay
插件。以下是一个简单的示例,展示如何调用支付接口:
import 'package:flutter/material.dart';
import 'package:upi_pay/upi_pay.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('UPI Pay Example'),
),
body: Center(
child: ElevatedButton(
onPressed: () => _initiatePayment(),
child: Text('Initiate Payment'),
),
),
),
);
}
Future<void> _initiatePayment() async {
try {
// 初始化 UPI Pay 配置
final UpiPayConfig config = UpiPayConfig(
environment: UpiPayEnvironment.sandbox, // 使用沙盒环境,生产环境请替换为 UpiPayEnvironment.production
merchantId: 'your_merchant_id', // 替换为你的商户ID
key: 'your_key', // 替换为你的密钥
returnUrl: 'your_return_url', // 替换为你的回调URL
);
// 设置支付参数
final UpiPayParams params = UpiPayParams(
amount: 100.0, // 支付金额,单位通常是元
currency: 'CNY', // 货币类型
orderId: 'order_12345', // 订单ID
description: 'Test Order', // 订单描述
);
// 调用支付接口
final UpiPayResult result = await UpiPay.pay(config: config, params: params);
// 处理支付结果
if (result.status == UpiPayStatus.success) {
// 支付成功
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Payment Successful'),
content: Text('Transaction ID: ${result.transactionId}'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('OK'),
),
],
);
},
);
} else if (result.status == UpiPayStatus.failed) {
// 支付失败
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Payment Failed'),
content: Text('Error: ${result.errorMessage}'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('OK'),
),
],
);
},
);
} else if (result.status == UpiPayStatus.canceled) {
// 支付取消
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Payment Canceled'),
content: Text('Payment was canceled by the user.'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('OK'),
),
],
);
},
);
}
} catch (e) {
// 处理异常
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Error'),
content: Text('An error occurred: $e'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('OK'),
),
],
);
},
);
}
}
}
在这个示例中,我们创建了一个简单的Flutter应用,其中包含一个按钮,用于触发支付流程。我们定义了支付配置和支付参数,并调用UpiPay.pay
方法来启动支付。支付完成后,我们根据返回的状态显示相应的对话框。
请注意,你需要将your_merchant_id
、your_key
和your_return_url
替换为你自己的实际值。同时,确保你已经正确配置了支付渠道和相关的密钥。
希望这个示例能够帮助你理解如何在Flutter项目中使用upi_pay
插件来实现统一支付接口。