项目组支付宝商户审核通过,要准备接入支付宝移动支付的能力,本文主要讲述一下android接入支付宝移动支付SDK的步骤。
前期准备
- 支付宝商户网站注册支付宝商户账号。
- 签约
移动支付
产品,等到审核通过。 - 秘钥的生成与上传。
注册和签约没什么好说的,由公司相关部门或人员进行操作就好了。这里重点讲一下密钥。
秘钥的生成:
下载SDK资源压缩包,解压后,里面会有openssl的文件夹,里面有个openssl.exe
,双击运行。
输入如下指令生成私钥:1
genrsa -out rsa_private_key.pem 1024
java开发者
(否则跳过此步骤)需要转换成PKCS8格式,再输入如下指令进行转换:1
pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt
可以看到下面的界面:右键点击openssl窗口上边边缘,选择“编辑→标记”,选中要复制的文字
,将PKCS8格式私钥复制下来,保存到一个文本中,后面会用到。
继续执行指令:1
rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
会得到公钥文本。
秘钥的上传:
登录支付宝商户账户,进入到我的商家服务
。
点击查询PID、KEY
,输入支付密码,进入到下面的页面:
记录下PID
。
点击RSA加密后面的添加密码
,这里我已经添加过,所以显示的查看秘钥。将生成步骤中生成的公钥文本粘贴进编辑框,点击保存,若显示上传成功
则代表成功。
接入SDK
- 将
alipaySDK-20160223.jar
jar包添加至项目中。 - 修改AndroidManifest.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<!-- 权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 当用户手机没有安装支付宝App时,会调用H5的页面进行支付,若不进行声明,则直接返回支付失败 -->
<activity
android:name="com.alipay.sdk.app.H5PayActivity"
android:configChanges="orientation|keyboardHidden|navigation"
android:exported="false"
android:screenOrientation="behind" >
</activity>
<activity
android:name="com.alipay.sdk.auth.AuthActivity"
android:configChanges="orientation|keyboardHidden|navigation"
android:exported="false"
android:screenOrientation="behind" >
</activity>
至此,SDK接入已完成。
调用支付
在支付宝Demo中,已经有了很详细的说明了。我们只需要填入3个字段:PARTNER
、SELLER
、RSA_PRIVATE
。PARTNER
就是上面步骤记录的PID
,SELLER
即是支付宝账号,RSA_PRIVATE
就是记录的PKCS8格式私钥。
这里贴一下Demo代码吧: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
228public class PayDemoActivity extends FragmentActivity {
// 商户PID
public static final String PARTNER = "";
// 商户收款账号
public static final String SELLER = "";
// 商户私钥,pkcs8格式
public static final String RSA_PRIVATE = "";
// 支付宝公钥
public static final String RSA_PUBLIC = "";
private static final int SDK_PAY_FLAG = 1;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@SuppressWarnings("unused")
public void handleMessage(Message msg) {
switch (msg.what) {
case SDK_PAY_FLAG: {
PayResult payResult = new PayResult((String) msg.obj);
/**
* 同步返回的结果必须放置到服务端进行验证(验证的规则请看https://doc.open.alipay.com/doc2/
* detail.htm?spm=0.0.0.0.xdvAU6&treeId=59&articleId=103665&
* docType=1) 建议商户依赖异步通知
*/
String resultInfo = payResult.getResult();// 同步返回需要验证的信息
String resultStatus = payResult.getResultStatus();
// 判断resultStatus 为“9000”则代表支付成功,具体状态码代表含义可参考接口文档
if (TextUtils.equals(resultStatus, "9000")) {
Toast.makeText(PayDemoActivity.this, "支付成功", Toast.LENGTH_SHORT).show();
} else {
// 判断resultStatus 为非"9000"则代表可能支付失败
// "8000"代表支付结果因为支付渠道原因或者系统原因还在等待支付结果确认,最终交易是否成功以服务端异步通知为准(小概率状态)
if (TextUtils.equals(resultStatus, "8000")) {
Toast.makeText(PayDemoActivity.this, "支付结果确认中", Toast.LENGTH_SHORT).show();
} else {
// 其他值就可以判断为支付失败,包括用户主动取消支付,或者系统返回的错误
Toast.makeText(PayDemoActivity.this, "支付失败", Toast.LENGTH_SHORT).show();
}
}
break;
}
default:
break;
}
}
;
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pay_main);
}
/**
* call alipay sdk pay. 调用SDK支付
*/
public void pay(View v) {
if (TextUtils.isEmpty(PARTNER) || TextUtils.isEmpty(RSA_PRIVATE) || TextUtils.isEmpty(SELLER)) {
new AlertDialog.Builder(this).setTitle("警告").setMessage("需要配置PARTNER | RSA_PRIVATE| SELLER")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialoginterface, int i) {
//
finish();
}
}).show();
return;
}
String orderInfo = getOrderInfo("测试的商品", "该测试商品的详细描述", "0.01");
/**
* 特别注意,这里的签名逻辑需要放在服务端,切勿将私钥泄露在代码中!
*/
String sign = sign(orderInfo);
try {
/**
* 仅需对sign 做URL编码
*/
sign = URLEncoder.encode(sign, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
/**
* 完整的符合支付宝参数规范的订单信息
*/
final String payInfo = orderInfo + "&sign=\"" + sign + "\"&" + getSignType();
Runnable payRunnable = new Runnable() {
@Override
public void run() {
// 构造PayTask 对象
PayTask alipay = new PayTask(PayDemoActivity.this);
// 调用支付接口,获取支付结果
String result = alipay.pay(payInfo, true);
Message msg = new Message();
msg.what = SDK_PAY_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
};
// 必须异步调用
Thread payThread = new Thread(payRunnable);
payThread.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支付) 【对应页面网页支付按钮】
*
* @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.meituan.com";
// url可以是一号店或者美团等第三方的购物wap站点,在该网站的支付过程中,支付宝sdk完成拦截支付
extras.putString("url", url);
intent.putExtras(extras);
startActivity(intent);
}
/**
* create the order info. 创建订单信息
*/
private String getOrderInfo(String subject, String body, String price) {
// 签约合作者身份ID
String orderInfo = "partner=" + "\"" + PARTNER + "\"";
// 签约卖家支付宝账号
orderInfo += "&seller_id=" + "\"" + SELLER + "\"";
// 商户网站唯一订单号
orderInfo += "&out_trade_no=" + "\"" + getOutTradeNo() + "\"";
// 商品名称
orderInfo += "&subject=" + "\"" + subject + "\"";
// 商品详情
orderInfo += "&body=" + "\"" + body + "\"";
// 商品金额
orderInfo += "&total_fee=" + "\"" + price + "\"";
// 服务器异步通知页面路径
orderInfo += "¬ify_url=" + "\"" + "http://notify.msp.hk/notify.htm" + "\"";
// 服务接口名称, 固定值
orderInfo += "&service=\"mobile.securitypay.pay\"";
// 支付类型, 固定值
orderInfo += "&payment_type=\"1\"";
// 参数编码, 固定值
orderInfo += "&_input_charset=\"utf-8\"";
// 设置未付款交易的超时时间
// 默认30分钟,一旦超时,该笔交易就会自动被关闭。
// 取值范围:1m~15d。
// m-分钟,h-小时,d-天,1c-当天(无论交易何时创建,都在0点关闭)。
// 该参数数值不接受小数点,如1.5h,可转换为90m。
orderInfo += "&it_b_pay=\"30m\"";
// extern_token为经过快登授权获取到的alipay_open_id,带上此参数用户将使用授权的账户进行支付
// orderInfo += "&extern_token=" + "\"" + extern_token + "\"";
// 支付宝处理完请求后,当前页面跳转到商户指定页面的路径,可空
orderInfo += "&return_url=\"m.alipay.com\"";
// 调用银行卡支付,需配置此参数,参与签名, 固定值 (需要签约《无线银行卡快捷支付》才能使用)
// orderInfo += "&paymethod=\"expressGateway\"";
return orderInfo;
}
/**
* get the out_trade_no for an order. 生成商户订单号,该值在商户端应保持唯一(可自定义格式规范)
*/
private 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;
}
/**
* sign the order info. 对订单信息进行签名
*
* @param content 待签名订单信息
*/
private String sign(String content) {
return SignUtils.sign(content, RSA_PRIVATE);
}
/**
* get the sign type we use. 获取签名方式
*/
private String getSignType() {
return "sign_type=\"RSA\"";
}
}
代码中有个注释需要特别注意:特别注意,这里的签名逻辑需要放在服务端,切勿将私钥泄露在代码中!
至此,整个集成就已经结束了,不得不说,支付宝的官方文档比微信好多了,哈哈~
补充
正式项目中需要将签名逻辑放在服务端,在我与服务端进行联调时,出现了很多问题,这里说几点:
- 服务端若是使用的是java,则使用PKCS8格式的秘钥,否则使用非PKCS8格式的秘钥。
- 请求参数可以无序,但是必须保证请求的参数与待签名参数的顺序一致。(非常坑,花了半天时间跟服务端联调才找到这个问题)