1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
import 'dart:async';
import 'dart:convert';
import 'package:alipay_kit/src/crypto/rsa.dart';
import 'package:alipay_kit/src/model/alipay_resp.dart';
import 'package:flutter/services.dart';
///
class Alipay {
///
Alipay._();
static Alipay get instance => _instance;
static final Alipay _instance = Alipay._();
static const String _METHOD_ISINSTALLED = 'isInstalled';
static const String _METHOD_PAY = 'pay';
static const String _METHOD_AUTH = 'auth';
static const String _METHOD_ONPAYRESP = 'onPayResp';
static const String _METHOD_ONAUTHRESP = 'onAuthResp';
static const String _ARGUMENT_KEY_ORDERINFO = 'orderInfo';
static const String _ARGUMENT_KEY_AUTHINFO = 'authInfo';
static const String _ARGUMENT_KEY_ISSHOWLOADING = 'isShowLoading';
static const String SIGNTYPE_RSA = 'RSA';
static const String SIGNTYPE_RSA2 = 'RSA2';
static const String AUTHTYPE_AUTHACCOUNT = 'AUTHACCOUNT';
static const String AUTHTYPE_LOGIN = 'LOGIN';
late final MethodChannel _channel =
const MethodChannel('v7lin.github.io/alipay_kit')
..setMethodCallHandler(_handleMethod);
final StreamController<AlipayResp> _payRespStreamController =
StreamController<AlipayResp>.broadcast();
final StreamController<AlipayResp> _authRespStreamController =
StreamController<AlipayResp>.broadcast();
Future<dynamic> _handleMethod(MethodCall call) async {
switch (call.method) {
case _METHOD_ONPAYRESP:
_payRespStreamController.add(AlipayResp.fromJson(
(call.arguments as Map<dynamic, dynamic>).cast<String, dynamic>()));
break;
case _METHOD_ONAUTHRESP:
_authRespStreamController.add(AlipayResp.fromJson(
(call.arguments as Map<dynamic, dynamic>).cast<String, dynamic>()));
break;
}
}
/// 支付
Stream<AlipayResp> payResp() {
return _payRespStreamController.stream;
}
/// 登录
Stream<AlipayResp> authResp() {
return _authRespStreamController.stream;
}
/// 检测支付宝是否已安装 - x.y.z-Android-Only 版本下 iOS 调用会直接抛出异常 No implementation [MissingPluginException]
Future<bool> isInstalled() async {
return await _channel.invokeMethod<bool?>(_METHOD_ISINSTALLED) ?? false;
}
/// 支付
Future<void> payOrderJson({
required String orderInfo,
String signType = SIGNTYPE_RSA2,
required String privateKey,
bool isShowLoading = true,
}) {
return payOrderMap(
orderInfo: json.decode(orderInfo) as Map<String, dynamic>,
signType: signType,
privateKey: privateKey,
isShowLoading: isShowLoading,
);
}
/// 支付
Future<void> payOrderMap({
required Map<String, dynamic> orderInfo,
String signType = SIGNTYPE_RSA2,
required String privateKey,
bool isShowLoading = true,
}) {
final String? charset = orderInfo['charset'] as String?;
final Encoding encoding = Encoding.getByName(charset) ?? utf8;
final Map<String, dynamic> clone = <String, dynamic>{
...orderInfo,
'sign_type': signType,
};
final String param = _param(clone, encoding);
final String sign = _sign(clone, signType, privateKey);
return payOrderSign(
orderInfo:
'$param&sign=${Uri.encodeQueryComponent(sign, encoding: encoding)}',
isShowLoading: isShowLoading,
);
}
/// 支付 - x.y.z-Android-Only 版本下 iOS 调用会直接抛出异常 No implementation [MissingPluginException]
Future<void> payOrderSign({
required String orderInfo,
bool isShowLoading = true,
}) {
return _channel.invokeMethod<void>(
_METHOD_PAY,
<String, dynamic>{
_ARGUMENT_KEY_ORDERINFO: orderInfo,
_ARGUMENT_KEY_ISSHOWLOADING: isShowLoading,
},
);
}
/// 登录
Future<void> auth({
required String appId, // 支付宝分配给开发者的应用ID
required String pid, // 签约的支付宝账号对应的支付宝唯一用户号,以2088开头的16位纯数字组成
required String targetId, // 商户标识该次用户授权请求的ID,该值在商户端应保持唯一
String authType =
AUTHTYPE_AUTHACCOUNT, // 标识授权类型,取值范围:AUTHACCOUNT 代表授权;LOGIN 代表登录
String signType =
SIGNTYPE_RSA2, // 商户生成签名字符串所使用的签名算法类型,目前支持 RSA2 和 RSA ,推荐使用 RSA2
required String privateKey,
bool isShowLoading = true,
}) {
assert(authType == AUTHTYPE_AUTHACCOUNT || authType == AUTHTYPE_LOGIN);
final Map<String, dynamic> authInfo = <String, dynamic>{
'apiname': 'com.alipay.account.auth',
'method': 'alipay.open.auth.sdk.code.get',
'app_id': appId,
'app_name': 'mc',
'biz_type': 'openservice',
'pid': pid,
'product_id': 'APP_FAST_LOGIN',
'scope': 'kuaijie',
'target_id': targetId,
'auth_type': authType,
};
authInfo['sign_type'] = signType;
final Encoding encoding = utf8; // utf-8
final String param = _param(authInfo, encoding);
final String sign = _sign(authInfo, signType, privateKey);
return authSign(
info: '$param&sign=${Uri.encodeQueryComponent(sign, encoding: encoding)}',
isShowLoading: isShowLoading,
);
}
/// 登录 - x.y.z-Android-Only 版本下 iOS 调用会直接抛出异常 No implementation [MissingPluginException]
Future<void> authSign({
required String info,
bool isShowLoading = true,
}) {
return _channel.invokeMethod<void>(
_METHOD_AUTH,
<String, dynamic>{
_ARGUMENT_KEY_AUTHINFO: info,
_ARGUMENT_KEY_ISSHOWLOADING: isShowLoading,
},
);
}
String _param(Map<String, dynamic> map, Encoding encoding) {
return map.entries
.map((MapEntry<String, dynamic> e) =>
'${e.key}=${Uri.encodeQueryComponent('${e.value}', encoding: encoding)}')
.join('&');
}
String _sign(Map<String, dynamic> map, String signType, String privateKey) {
// 参数排序
final List<String> keys = map.keys.toList();
keys.sort();
final String content = keys.map((String e) => '$e=${map[e]}').join('&');
String sign;
switch (signType) {
case SIGNTYPE_RSA:
sign = base64
.encode(RsaSigner.sha1Rsa(privateKey).sign(utf8.encode(content)));
break;
case SIGNTYPE_RSA2:
sign = base64
.encode(RsaSigner.sha256Rsa(privateKey).sign(utf8.encode(content)));
break;
default:
throw UnsupportedError('Alipay sign_type($signType) is not supported!');
}
return sign;
}
}