您的位置:首页 > 其它

2017安卓开发接入支付宝支付功能详解,真正做到完全翻译支付宝sdk开发应用

2017-09-19 15:15 691 查看
对于初级开发者而言,吗德支付宝的开发文档最新版本和老版本写的一样狗屎的狠,网络上很多专家写的博客虽然有一点多余解释,但一个比一个惜字如金,来回调用的方法都看不懂有木有!更有甚者,有些大神完全脱离开发文档demo,自由发挥新篇章,浏览诸多,心有猛火。老子自己重新翻译支付表的开发文档!本文就是我怒火烧心后的产物,绝对完全剖析,Android在线支付Alipay(支付宝)开发,废话少说,上demo
package com.example.alipayinstense;

import java.util.Map;

import com.alipay.sdk.app.AuthTask;
import com.alipay.sdk.app.PayTask;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
//这里的mainactivity就是官方的PayDemoActivity ;
//里面的方法都在这里,就是类名不一样而已,大可不必纠结
public class MainActivity extends ActionBarActivity implements OnClickListener {

private TextView tv;
private Button buy;

/** 支付宝支付业务:入参app_id */
public static final String APPID = "2016083000128338";

/** 支付宝账户登录授权业务:入参pid值 */
public static final String PID = "2088502954592834";
/** 支付宝账户登录授权业务:入参target_id值 商户收款账号*///此处应该不起任何作用,下文无调用
public static final String TARGET_ID = "";

/** 商户私钥,pkcs8格式 */
/** 如下私钥,RSA2_PRIVATE 或者 RSA_PRIVATE 只需要填入一个 */
/** 如果商户两个都设置了,优先使用 RSA2_PRIVATE */
/** RSA2_PRIVATE 可以保证商户交易在更加安全的环境下进行,建议使用 RSA2_PRIVATE */
/** 获取 RSA2_PRIVATE,建议使用支付宝提供的公私钥生成工具生成, */
/** 工具地址:https://doc.open.alipay.com/docs/doc.htm?treeId=291&articleId=106097&docType=1 */
//公钥已上传至支付宝平台,私钥已保存本地,这是本应用私钥
public static final String RSA2_PRIVATE = "MIIEvasefDA.....9w0BAQEFAASCBKYwgg....RUJCt7xt+EaJRCX/mGktAi5BQ4iwKTQea8PPDK9m+DzyfVECgYAtKMhyH...86CocnJrNqbtyoq......0pDStHa98LrihY+XeiyXUME6.....HBjey/..kBjmUb30PYdHU1OfTL3qHvy9MAE1U6GyOJgahZCPYHeqg==";
//rsa没用
public static final String RSA_PRIVATE = "";

private static final int SDK_PAY_FLAG = 1;//sdk正常运行了吗?
private static final int SDK_AUTH_FLAG = 2;//sdk确认了吗?

private Activity activity;//这里声明一个活动是干什么的?
private String orderNo;//订单详情

@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@SuppressWarnings("unused")
public void handleMessage(Message msg) {
switch (msg.what) {
case SDK_PAY_FLAG: {//如果消息是 SDK正常运行
@SuppressWarnings("unchecked")
//将随该消息附带的msg.obj强转回map中,建立新的payresult支付结果
PayResult payResult = new PayResult((Map<String, String>) msg.obj);
/**
对于支付结果,请商户依赖服务端的异步通知结果。同步通知结果,仅作为支付结束的通知。
*/
String resultInfo = payResult.getResult();// 同步返回需要验证的信息。从支付结果中取到resultinfo
String resultStatus = payResult.getResultStatus();//得到resultstatus
// 判断resultStatus 为9000则代表支付成功
if (TextUtils.equals(resultStatus, "9000")) {
// 该笔订单是否真实支付成功,需要依赖服务端的异步通知。
Toast.makeText(MainActivity.this, "支付成功", Toast.LENGTH_SHORT).show();
} else {
// 该笔订单真实的支付结果,需要依赖服务端的异步通知。
Toast.makeText(MainActivity.this, "支付失败", Toast.LENGTH_SHORT).show();
}
break;
}
case SDK_AUTH_FLAG: {//如果消息是SDK已经确认
@SuppressWarnings("unchecked")
//将随该消息附带的msg.obj强转回MAP,第二个参数为“去除消息中包含的括号吗?(boolean)”
AuthResult authResult = new AuthResult((Map<String, String>) msg.obj, true);//新建自定义的authResult对象
//利用Authresult中的自定义方法的到resultstatus
String resultStatus = authResult.getResultStatus();

// 判断resultStatus 为“9000”且result_code
// 为“200”则代表授权成功,具体状态码代表含义可参考授权接口文档
if (TextUtils.equals(resultStatus, "9000") && TextUtils.equals(authResult.getResultCode(), "200")) {
// 获取alipay_open_id,调支付时作为参数extern_token 的value
// 传入,则支付账户为该授权账户
Toast.makeText(MainActivity.this,
"授权成功\n" + String.format("authCode:%s", authResult.getAuthCode()), Toast.LENGTH_SHORT)
.show();
} else {
// 其他状态值则为授权失败
Toast.makeText(MainActivity.this,
"授权失败" + String.format("authCode:%s", authResult.getAuthCode()), Toast.LENGTH_SHORT).show();

}
break;
}
default:
break;
}
};
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);//当前活动界面赋值
initView();//初始化控件
}

private void initView() {
// TODO Auto-generated method stub

}

/**
* 支付宝pay方法
* 支付宝支付业务
*
* @param v
*/
public void payV2(View v) {
/**
* 如果APPID是空值或私钥两个全是空,则弹出警告对话框告诉开发者“需要配置APPID|RSA_PRIVATE”
*/
if (TextUtils.isEmpty(APPID) || (TextUtils.isEmpty(RSA2_PRIVATE) && TextUtils.isEmpty(RSA_PRIVATE))) {
new AlertDialog.Builder(this).setTitle("警告").setMessage("需要配置APPID | RSA_PRIVATE")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialoginterface, int i) {
//
finish();
}
}).show();
return;
}

/**
* 这里只是为了方便直接向商户展示支付宝的整个支付流程;所以Demo中加签过程直接放在客户端完成;
* 真实App里,privateKey等数据严禁放在客户端,加签过程务必要放在服务端完成;
* 防止商户私密数据泄露,造成不必要的资金损失,及面临各种安全风险;
*
* orderInfo的获取必须来自服务端;防止本地orderInfo被认为更改,例如买个冰箱改成买个鸡蛋
*/
boolean rsa2 = (RSA2_PRIVATE.length() > 0);//rsa2私钥已经被赋值
//在这里,传入APPID和私钥,得到请求map,即包含支付订单信息的map
Map<String, String> params = OrderInfoUtil2_0.buildOrderParamMap(APPID, rsa2);
//将map解析成一个String类型的支付订单
String orderParam = OrderInfoUtil2_0.buildOrderParam(params);
//是rsa2类型的私钥吗?如果rsa2私钥已经被赋值,那么privateKey就被设置成rsa2,否则就被设置成rsa。这就是支付宝说的“如果你有俩私钥,优先使用RSa2私钥,靠!”
String privateKey = rsa2 ? RSA2_PRIVATE : RSA_PRIVATE;
//对,privateKey就是商户私钥!通过此方法对商户的私钥进行“签名”处理,处理后就会生成(返回)一个sign=GKHJL%……&*的一大串字串
String sign = OrderInfoUtil2_0.getSign(params, privateKey, rsa2);
//最终,结合orderparam参数与sign签名字串,搞成orderInfo字串;
final String orderInfo = orderParam + "&" + sign;
//新开一个线程,将orderInfo字串传入到PayTask任务中去
Runnable payRunnable = new Runnable() {

@Override
public void run() {
//新建一个PAyTask对象
PayTask alipay = new PayTask(MainActivity.this);
Map<String, String> result = alipay.payV2(orderInfo, true);
Log.i("msp", result.toString());

Message msg = new Message();
msg.what = SDK_PAY_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
};

Thread payThread = new Thread(payRunnable);
payThread.start();
}

/**
* 支付宝账户授权业务
*
* @param v
*/
public void authV2(View v) {
if (TextUtils.isEmpty(PID) || TextUtils.isEmpty(APPID)
|| (TextUtils.isEmpty(RSA2_PRIVATE) && TextUtils.isEmpty(RSA_PRIVATE))
|| TextUtils.isEmpty(TARGET_ID)) {
new AlertDialog.Builder(this).setTitle("警告").setMessage("需要配置PARTNER |APP_ID| RSA_PRIVATE| TARGET_ID")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialoginterface, int i) {
}
}).show();
return;
}

/**
* 这里只是为了方便直接向商户展示支付宝的整个支付流程;所以Demo中加签过程直接放在客户端完成;
* 真实App里,privateKey等数据严禁放在客户端,加签过程务必要放在服务端完成;
* 防止商户私密数据泄露,造成不必要的资金损失,及面临各种安全风险;
*
* authInfo的获取必须来自服务端;
*/
boolean rsa2 = (RSA2_PRIVATE.length() > 0);
Map<String, String> authInfoMap = OrderInfoUtil2_0.buildAuthInfoMap(PID, APPID, TARGET_ID, rsa2);
//利用这个方法,我可以将map解析成一个String类型的支付订单,即得到info
String info = OrderInfoUtil2_0.buildOrderParam(authInfoMap);

String privateKey = rsa2 ? RSA2_PRIVATE : RSA_PRIVATE;
//对支付参数信息进行签名 传入进来一个Map,一个rsaKey,
//并且询问是否是rsa2格式的私钥 这个rsaKey是商户的私钥 就是说通过此方法对商户的私钥进行“签名”处理,
//处理后就会生成(返回)一个sign=GKHJL%……&*的一大串字串
String sign = OrderInfoUtil2_0.getSign(authInfoMap, privateKey, rsa2);
final String authInfo = info + "&" + sign;
//得到orderInfo后,我们开启新线程,发送相关信息
Runnable authRunnable = new Runnable() {

@Override
public void run() {
// 构造AuthTask 对象
AuthTask authTask = new AuthTask(MainActivity.this);
// 调用授权接口,获取授权结果
Map<String, String> result = authTask.authV2(authInfo, true);

Message msg = new Message();
msg.what = SDK_AUTH_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
};

// 必须异步调用
Thread authThread = new Thread(authRunnable);
authThread.start();
}

/**
* get the sdk version. 获取SDK版本号
*
*/
public void getSDKVersion() {
PayTask payTask = new PayTask(this);
String version = payTask.getVersion();
Toast.makeText(this, version, Toast.LENGTH_SHORT).show();
}

/**
* 原生的H5(手机网页版支付切natvie支付) 【对应页面网页支付按钮】
* 意思就是我应用里面有一个h5的页面,页面里面有好多
* 商品售卖,当我点击h5中的末一个商品支付时,支付表就会在此时调出来
* 如果app中没有webView实现h5,那完全用不到此方法
*
* @param v
*/
public void h5Pay(View v) {
Intent intent = new Intent(this, H5PayDemoActivity.class);
Bundle extras = new Bundle();
/**
* url是测试的网站,在app内部打开页面是基于webview打开的,demo中的webview是H5PayDemoActivity,
* demo中拦截url进行支付的逻辑是在H5PayDemoActivity中shouldOverrideUrlLoading方法实现,
* 商户可以根据自己的需求来实现
*/
String url = "http://m.taobao.com";
// url可以是一号店或者淘宝等第三方的购物wap站点,在该网站的支付过程中,支付宝sdk完成拦截支付
extras.putString("url", url);
intent.putExtras(extras);
startActivity(intent);
}

@Override
public void onClick(View v) {
// TODO Auto-generated method stub

}

}
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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284

接下来是AuthResult类
package com.alipay.sdk.pay.demo;

import java.util.Map;

import android.text.TextUtils;
/**
* 确定结果
* @author Administrator
*
*/
public class AuthResult {

private String resultStatus;//结果状态
private String result;//结果
private String memo;//备忘录
private String resultCode;//结果码
private String authCode;//确认码
private String alipayOpenId;//支付宝开放ID
/**
* 确认结果,参数为键值对全是string型的’结果行‘和一个boolean类型的参数'去除所有括号了吗'
* @param rawResult
* @param removeBrackets
*/

public AuthResult(Map<String, String> rawResult, boolean removeBrackets) {
if (rawResult == null) {//传入的结果行map不为空才往下进行
return;
}

for (String key : rawResult.keySet()) {
if (TextUtils.equals(key, "resultStatus")) {//获取传入map中的键值对为resultStatus的值放入到resultStatus中(结果状态信息)
resultStatus = rawResult.get(key);
} else if (TextUtils.equals(key, "result")) {//获取传入map中的键值对的结果
result = rawResult.get(key);
} else if (TextUtils.equals(key, "memo")) {////获取传入map中的备注(备忘录)
memo = rawResult.get(key);
}
}

String[] resultValue = result.split("&");//将结果用&符号拆分
//对传入数组进行遍历
for (String value : resultValue) {
if (value.startsWith("alipay_open_id")) {//如果以alipay_open_id(支付宝开放id)开始的
//此时的value是以alipay_open_id开头的值;支付宝的开放id=截取到的alipay_open_id字串去除括号
alipayOpenId = removeBrackets(getValue("alipay_open_id=", value), removeBrackets);
continue;
}
if (value.startsWith("auth_code")) {//如果循环进行到确认码"auth_code"
authCode = removeBrackets(getValue("auth_code=", value), removeBrackets);//处理得到确认码
continue;
}
if (value.startsWith("result_code")) {//如果解析到结果码
resultCode = removeBrackets(getValue("result_code=", value), removeBrackets);//处理得到结果码
continue;
}
}

}
/**
* 移除括号的方法
* @param str 传入的字符串
* @param remove 移除吗?
* @return 返回处理过后的字符串
*/
private String removeBrackets(String str, boolean remove) {
if (remove) {//如果是移除的话
if (!TextUtils.isEmpty(str)) {//如果传入的字符串不是空的话,则将对字串进行一下操作
if (str.startsWith("\"")) {
str = str.replaceFirst("\"", "");//移除第一个斜杠
}
if (str.endsWith("\"")) {
str = str.substring(0, str.length() - 1);//移除最后的斜杠
}
}
}
return str;//返回处理过后的字符串
}

@Override
public String toString() {
return "resultStatus={" + resultStatus + "};memo={" + memo + "};result={" + result + "}";
}
/**
* 获取值得方法
* @param header 头部字符串
* @param data 数据字符串
* @return
*/
private String getValue(String header, String data) {
return data.substring(header.length(), data.length());//将data从截取头部的长度处截取到传入data的末尾
}

/**
* 获取String类型的结果状态
* @return the resultStatus
*/
public String getResultStatus() {
return resultStatus;
}

/**
* 获取备注
* @return the memo
*/
public String getMemo() {
return memo;
}

/**
* 获取结果
* @return the result
*/
public String getResult() {
return result;
}

/**
* 获取结果码
* @return the resultCode
*/
public String getResultCode() {
return resultCode;
}

/**
* 获取确认码
* @return the authCode
*/
public String getAuthCode() {
return authCode;
}

/**
* 获取支付宝开放支付ID
* @return the alipayOpenId
*/
public String getAlipayOpenId() {
return alipayOpenId;
}
}
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

接下来是OrderInfoUtil2_0
package com.example.alipayinstense;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;

public class OrderInfoUtil2_0 {

/**
* 自定义orderInfo请求信息对象
* 构造授权参数列表
* 列表里面的参数有PID,APPID,TARGETID,和一个boolean值“是否设置了rsa2私钥”
* 就是说我这个支付参数map中要有这些参数,并且给他们赋值
* @param pid
* @param app_id
* @param target_id
* @return
*/
public static Map<String, String> buildAuthInfoMap(String pid, String app_id, String target_id, boolean rsa2) {
Map<String, String> keyValues = new HashMap<String, String>();

// 商户签约拿到的app_id,如:2013081700024223
keyValues.put("app_id", app_id);

// 商户签约拿到的pid,如:2088102123816631
keyValues.put("pid", pid);

// 服务接口名称, 固定值
keyValues.put("apiname", "com.alipay.account.auth");

// 商户类型标识, 固定值
keyValues.put("app_name", "mc");

// 业务类型, 固定值
keyValues.put("biz_type", "openservice");

// 产品码, 固定值
keyValues.put("product_id", "APP_FAST_LOGIN");

// 授权范围, 固定值
keyValues.put("scope", "kuaijie");

// 商户唯一标识,如:kkkkk091125//这个是干什么用的????????
keyValues.put("target_id", target_id);

// 授权类型, 固定值
keyValues.put("auth_type", "AUTHACCOUNT");

// 签名类型
keyValues.put("sign_type", rsa2 ? "RSA2" : "RSA");

return keyValues;
}

/**
* 构造支付订单参数列表
* 就是说我这个支付订单是由这个map组成的,map中要有这些几个参数,并且给他们赋值
* @param pid
* @param app_id
* @param target_id
* @return
*/
public static Map<String, String> buildOrderParamMap(String app_id, boolean rsa2) {
Map<String, String> keyValues = new HashMap<String, String>();

keyValues.put("app_id", app_id);

keyValues.put("biz_content", "{\"timeout_express\":\"30m\",\"product_code\":\"QUICK_MSECURITY_PAY\",\"total_amount\":\"0.01\",\"subject\":\"1\",\"body\":\"我是测试数据\",\"out_trade_no\":\"" + getOutTradeNo() +  "\"}");

keyValues.put("charset", "utf-8");

keyValues.put("method", "alipay.trade.app.pay");

keyValues.put("sign_type", rsa2 ? "RSA2" : "RSA");//是rsa2私钥还是rsa私钥

keyValues.put("timestamp", "2016-07-29 16:55:53");

keyValues.put("version", "1.0");

return keyValues;
}

/**
* 构造支付订单参数信息
* 传入进来一个Map参数,就是说利用这个方法,我可以将map解析成一个String类型的支付订单
* @param map
* 支付订单参数
* @return
*/
public static String buildOrderParam(Map<String, String> map) {
List<String> keys = new ArrayList<String>(map.keySet());

StringBuilder sb = new StringBuilder();
for (int i = 0; i < keys.size() - 1; i++) {
String key = keys.get(i);
String value = map.get(key);
sb.append(buildKeyValue(key, value, true));
sb.append("&");
}

String tailKey = keys.get(keys.size() - 1);
String tailValue = map.get(tailKey);
sb.append(buildKeyValue(tailKey, tailValue, true));

return sb.toString();
}

/**
* 拼接键值对
* 将传入进来的两个字符串利用此方法拼接成key=value的形式
* 该方法咨询是否转码(isEncode),如果需要对value进行转码的话,将会将其转为UTF—8格式;
* @param key
* @param value
* @param isEncode
* @return
*/
private static String buildKeyValue(String key, String value, boolean isEncode) {
StringBuilder sb = new StringBuilder();
sb.append(key);
sb.append("=");
if (isEncode) {
try {
sb.append(URLEncoder.encode(value, "UTF-8"));
} catch (UnsupportedEncodingException e) {
sb.append(value);
}
} else {
sb.append(value);
}
return sb.toString();
}

/**
* 对支付参数信息进行签名
* 传入进来一个Map,一个rsaKey,并且询问是否是rsa2格式的私钥
* 这个rsaKey是商户的私钥
* 就是说通过此方法对商户的私钥进行“签名”处理,处理后就会生成(返回)一个sign=GKHJL%……&*的一大串字串
* @param map
*            待签名授权信息
*
* @return
*/
public static String getSign(Map<String, String> map, String rsaKey, boolean rsa2) {
List<String> keys = new ArrayList<String>(map.keySet());
// key排序
Collections.sort(keys);

StringBuilder authInfo = new StringBuilder();
for (int i = 0; i < keys.size() - 1; i++) {
String key = keys.get(i);
String value = map.get(key);
authInfo.append(buildKeyValue(key, value, false));
authInfo.append("&");
}

String tailKey = keys.get(keys.size() - 1);
String tailValue = map.get(tailKey);
authInfo.append(buildKeyValue(tailKey, tailValue, false));

String oriSign = SignUtils.sign(authInfo.toString(), rsaKey, rsa2);
String encodedSign = "";

try {
encodedSign = URLEncoder.encode(oriSign, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "sign=" + encodedSign;
}

/**
* 利用此方法获取到一个外部订单号"OutTradeNo"
* 要求外部订单号必须唯一。
* @return
*/
private static String getOutTradeNo() {
SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss", Locale.getDefault());
Date date = new Date();
String key = format.format(date);

Random r = new Random();
key = key + r.nextInt();
key = key.substring(0, 15);
return key;
}

}
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

利用最后两个类直接替换掉官方demo,会有意想不到的效果,你会发现PayDemoActivity的思路是那么的清晰。 

看完这些全新的注释,是不是发现原来安卓应用支付宝开发原来很简单? 

shirt!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: