我个人将集成微信支付的过程分成4个步骤: 微信官方api文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
1.配置各种信息
2.拼凑预订单信息,访问微信服务器生成预订单,主要是为了得到prepared_id —– 建议在自己的服务器操作
3.根据得到的prepared_id及其他信息进行二次签名,调起微信sdk支付 — 前部分建议在服务器操作,后面部分在app端操作
4.根据回调的支付结果执行不同的逻辑
接下来具体说说各个步骤
1、配置各种信息 如在支付Activity中PayActivity(名字自己定)和微信回调的WXPayEntryActivity(这个类的名字不允许改变,包名固定为你应用的包名+wxapi)功能清单文件配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<!--微信支付回调界面-->
<intent-filter>
<category android:name=
"android.intent.category.DEFAULT"
>
<data android:scheme=
"你的应用appid"
> <!-- 需修改 -->
</data></category></action></intent-filter>
</activity>
<!--微信支付界面-->
<intent-filter>
<category android:name=
"android.intent.category.DEFAULT"
>
<data android:scheme=
"你的应用appid"
> <!-- 需修改 -->
</data></category></action></intent-filter>
</activity>
|
当然,权限什么的就不说了,别忘了哈
2.拼凑预订单信息,访问微信服务器生成预订单,主要是为了得到prepared_id,如下的代码是在客户端生成的实例,如果不需要在客户端操作,请无视
生成预订单有10个必选参数分别为 (注意参数名不能改变,post请求,以xml格式)
appid(应用id)、 mch_id(商户号)、 nonce_str(随机字符串,参照我下面的代码生成)、 sign(签名,参照我下面的代码签名)、 body(商品描述)、
out_trade_no(商户订单号,自己生成,唯一)、 total_fee(总金额,单位:分,不能像支付宝有0.01这种)、 spbill_create_ip(终端ip,参照下面ipv4的获取)、 notify_url(微信支付结果异步通知的地址)、 trade_type(交易类型,app的填APP即可)
官方的参数示例
1
2
3
4
5
6
7
8
9
10
11
12
|
<xml>
wx2421b1c4370ec43b</appid>
支付测试</attach>APP支付测试
<mch_id>
10000100
</mch_id>
<nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str>
<notify_url>http:
//wxpay.weixin.qq.com/pub_v2/pay/notify.v2.php</notify_url>
<out_trade_no>
1415659990
</out_trade_no>
<spbill_create_ip>
14.23
.
150.211
</spbill_create_ip>
<total_fee>
1
</total_fee>
<trade_type>APP</trade_type>
<sign>0CB01533B8C1EF103065174F50BCA001</sign>
</xml>
|
下面是几个参数的生成:
随机字符串的生成方式
1
2
3
4
5
6
7
8
|
/**
* 生成随机字符串,算法自己考虑
* @return
*/
private
String genNonceStr() {
Random random =
new
Random();
return
MD5.getMessageDigest(String.valueOf(random.nextInt(
10000
)).getBytes());
}
|
订单号的生成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/**
* get the out_trade_no for an order. 生成商户订单号,该值在商户端应保持唯一(可自定义格式规范)
*
*/
private
String getWXOutTradeNo() {
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;
}
|
ipv4的获取方式:
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
|
/**
* 获取自己手机的ipv4地址
* @return
*/
public
String getIp() {
try
{
for
(Enumeration<networkinterface> en = NetworkInterface
.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for
(Enumeration<inetaddress> ipAddr = intf.getInetAddresses(); ipAddr
.hasMoreElements();) {
InetAddress inetAddress = ipAddr.nextElement();
// ipv4地址
if
(!inetAddress.isLoopbackAddress()
&& InetAddressUtils.isIPv4Address(inetAddress
.getHostAddress())) {
Log.e(
"TAG"
,
"ipv4="
+ inetAddress.getHostAddress());
return
inetAddress.getHostAddress();
}
}
}
}
catch
(Exception ex) {
}
return
"192.168.1.0"
;
}</inetaddress></networkinterface>
|
签名的生成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/**
* 生成package参数,就是签名参数
* @param params
* @return
*/
private
String genPackage(List<namevaluepair> params) {
StringBuilder sb =
new
StringBuilder();
for
(
int
i =
0
; i < params.size(); i++) {
sb.append(params.get(i).getName());
sb.append(
'='
);
sb.append(params.get(i).getValue());
sb.append(
'&'
);
}
sb.append(
"key="
);
sb.append(Constants.API_KEY);
// 注意:不能hardcode在客户端,建议genPackage这个过程都由服务器端完成
return
MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
}</namevaluepair>
|
生成xml字符串的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
/**
* 生成xml文件字符串
* @param packageParams
* @return
*/
private
String toXml(List<namevaluepair> packageParams) {
StringBuffer xml =
new
StringBuffer(
""
);
xml.append(
"<xml>"
);
for
(
int
i =
0
;i < packageParams.size();i++){
NameValuePair nameValuePair = packageParams.get(i);
xml.append(
"<"
+ nameValuePair.getName() +
">"
+ nameValuePair.getValue() +
"<!--"
+ nameValuePair.getName() +
"-->"
);
if
(i == packageParams.size() -
1
){
xml.append(
"</xml>"
);
}
}
return
new
String(xml);
}</namevaluepair>
|
接下来就可以将上面的10个参数生成xml文件字符串上传
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
|
/**
* 生成预订单的信息
* @return
*/
private
String getProductArgs(){
String nonceStr = genNonceStr();
//随机字符串
List<namevaluepair> packageParams =
new
LinkedList<namevaluepair>();
packageParams
.add(
new
BasicNameValuePair(
"appid"
, Constants.APP_ID));
packageParams.add(
new
BasicNameValuePair(
"body"
, aliOrderBO.getName()));
//商品名称
// packageParams.add(new BasicNameValuePair("input_charset", "UTF-8"));
packageParams.add(
new
BasicNameValuePair(
"detail"
, aliOrderBO.getDescribe()));
//商品详情
packageParams
.add(
new
BasicNameValuePair(
"mch_id"
, Constants.MCH_ID));
//商户号
packageParams.add(
new
BasicNameValuePair(
"nonce_str"
, nonceStr));
//随机字符串
packageParams.add(
new
BasicNameValuePair(
"notify_url"
, UrlStrings.getUrl(UrlIds.WXPAY_NOTIFY)));
//接收服务器异步结果的url
String orderCode = getWXOutTradeNo();
//生成商户订单号
((MyApplication) getApplication()).setWxTradeNo(orderCode);
//将订单号保存
packageParams.add(
new
BasicNameValuePair(
"out_trade_no"
,
//商户订单号
orderCode));
packageParams.add(
new
BasicNameValuePair(
"spbill_create_ip"
,getIp()));
//用户终端IP
double
totalFee = aliOrderBO.getPrice()*
100
;
//价格,单位是分
packageParams.add(
new
BasicNameValuePair(
"total_fee"
, String.valueOf((
int
)totalFee)));
//订单总金额
packageParams.add(
new
BasicNameValuePair(
"trade_type"
,
"APP"
));
//交易类型
String packageValue = genPackage(packageParams);
packageParams.add(
new
BasicNameValuePair(
"sign"
, packageValue));
try
{
return
new
String(toXml(packageParams).toString().getBytes(),
"ISO8859-1"
);
}
catch
(UnsupportedEncodingException e) {
e.printStackTrace();
}
return
null
;
}</namevaluepair></namevaluepair>
|
上传预订单信息给微信服务器
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
|
new
GetPrepayIdTask().execute(url, entry);
//访问微信服务器
//上传的类
private
class
GetPrepayIdTask
extends
AsyncTask<string, preparewxpaybean=
""
> {
@Override
protected
void
onPreExecute() {
}
@Override
protected
void
onPostExecute(PrepareWXPayBean result) {
//这个PrepareWXPayBean是服务器返回的结果,自定义的类
if
(result.localRetCode == PrepareWXPayBean.LocalRetCode.ERR_OK) {
sendPayReq(result);
}
else
{
Log.e(
"TAG"
,
"error"
);
}
}
@Override
protected
void
onCancelled() {
super
.onCancelled();
}
@Override
protected
PrepareWXPayBean doInBackground(String... params) {
PrepareWXPayBean result =
new
PrepareWXPayBean();
if
(params ==
null
|| params.length <=
0
) {
result.localRetCode = PrepareWXPayBean.LocalRetCode.ERR_ARGU;
return
result;
}
byte
[] buf = Util.httpPost(params[
0
],params[
1
]);
if
(buf ==
null
|| buf.length ==
0
) {
result.localRetCode = PrepareWXPayBean.LocalRetCode.ERR_HTTP;
return
result;
}
String content =
new
String(buf);
result = decodeXML(content);
result.localRetCode = PrepareWXPayBean.LocalRetCode.ERR_OK;
return
result;
}
}
</string,>
|
3.解析返回的预订单信息再次签名,然后调起微信sdk(前面建议的服务器完成,如果不需要,请无视)
返回结果示例(请求成功的结果,其他情况请参考api):
1
2
3
4
5
6
7
8
9
10
11
|
<xml>
<return_code><!--[CDATA[SUCCESS]]--></return_code>
<return_msg><!--[CDATA[OK]]--></return_msg>
<!--[CDATA[wx2421b1c4370ec43b]]--></appid>
<mch_id><!--[CDATA[
10000100
]]--></mch_id>
<nonce_str><!--[CDATA[IITRi8Iabbblz1Jc]]--></nonce_str>
<sign><!--[CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]--></sign>
<result_code><!--[CDATA[SUCCESS]]--></result_code>
<prepay_id><!--[CDATA[wx201411101639507cbf6ffd8b0779950874]]--></prepay_id>
<trade_type><!--[CDATA[APP]]--></trade_type>
</xml>
|
解析结果的方法(这只是个示例,具体方式请自行考虑):
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
|
/**
* 解析xml
* @param content
* @return
*/
private
PrepareWXPayBean decodeXML(String content) {
XmlPullParser parser = Xml.newPullParser();
PrepareWXPayBean bean =
new
PrepareWXPayBean();
try
{
parser.setInput(
new
StringReader(content));
int
event = parser.getEventType();
while
(event != XmlPullParser.END_DOCUMENT) {
switch
(event) {
case
XmlPullParser.START_DOCUMENT:
break
;
case
XmlPullParser.START_TAG:
if
(
"return_code"
.equals(parser.getName())) {
parser.next();
bean.setReturn_code(parser.getText());
}
else
if
(
"return_msg"
.equals(parser.getName())) {
parser.next();
bean.setReturn_msg(parser.getText());
}
else
if
(
"appid"
.equals(parser.getName())) {
parser.next();
bean.setAppid(parser.getText());
}
else
if
(
"mch_id"
.equals(parser.getName())) {
parser.next();
bean.setMch_id(parser.getText());
}
else
if
(
"nonce_str"
.equals(parser.getName())) {
parser.next();
bean.setNonce_str(parser.getText());
}
else
if
(
"sign"
.equals(parser.getName())) {
parser.next();
bean.setSign(parser.getText());
}
else
if
(
"result_code"
.equals(parser.getName())) {
parser.next();
bean.setResult_code(parser.getText());
}
else
if
(
"prepay_id"
.equals(parser.getName())) {
parser.next();
bean.setPrepay_id(parser.getText());
}
else
if
(
"trade_type"
.equals(parser.getName())) {
parser.next();
bean.setTrade_type(parser.getText());
}
break
;
case
XmlPullParser.END_TAG:
break
;
}
event = parser.next();
}
}
catch
(Exception e) {
e.printStackTrace();
}
return
bean;
}
|
解析完成后进行二次签名,签名方法如下 key就是api秘钥 key设置路径:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置
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
|
private
static
final
char
SPLIT =
'&'
;
/**
* 微信开放平台和商户约定的密钥
*
* 注意:不能hardcode在客户端,建议genSign这个过程由服务器端完成
*/
private
String genSign(PayReq req) {
StringBuilder sb =
new
StringBuilder();
sb.append(
"appid="
);
sb.append(req.appId);
sb.append(SPLIT);
sb.append(
"noncestr="
);
sb.append(req.nonceStr);
sb.append(SPLIT);
sb.append(
"package="
);
sb.append(req.packageValue);
sb.append(SPLIT);
sb.append(
"partnerid="
);
sb.append(req.partnerId);
sb.append(SPLIT);
sb.append(
"prepayid="
);
sb.append(req.prepayId);
sb.append(SPLIT);
sb.append(
"timestamp="
);
sb.append(req.timeStamp);
sb.append(SPLIT);
sb.append(
"key="
);
sb.append(Constants.API_KEY);
// 注意:不能hardcode在客户端,建议genSign这个过程都由服务器端完成
return
MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
}
|
生成时间戳的方法:
1
2
3
4
5
6
7
|
/**
* 生成时间戳
* @return
*/
private
String genTimeStamp() {
return
String.valueOf(System.currentTimeMillis() /
1000
);
}
|
然后就可以调起微信sdk支付啦
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
|
private
void
sendPayReq(PrepareWXPayBean result) {
if
(
"FAIL"
.equals(result.getResult_code())) {
Log.e(TAG,
"sendPayReq fail, retCode = "
+ result.getResult_code() +
", retmsg = "
+ result.getReturn_msg());
return
;
}
String prepare_id = result.getPrepay_id();
Log.d(TAG,
"sendPayReq, prepare_id = "
+ prepare_id +
", sign = "
+ result.getSign());
if
(prepare_id ==
null
|| prepare_id.length() ==
0
) {
Log.e(TAG,
"sendPayReq fail, prepare_id is empty"
);
return
;
}
PayReq req =
new
PayReq();
req.appId = result.getAppid();
req.partnerId = result.getMch_id();
//商户号
req.prepayId = prepare_id;
req.nonceStr = result.getNonce_str();
//随机字符串
req.timeStamp = genTimeStamp();
//时间戳
req.packageValue =
"Sign=WXPay"
;
//固定字符串
req.sign = genSign(req);
//签名
//req.extData = "app data"; // optional,微信不处理该字段,会在PayResp结构体中回传该字段
// 在支付之前,如果应用没有注册到微信,应该先调用IWXMsg.registerApp将应用注册到微信
api.registerApp(Constants.APP_ID);
api.sendReq(req);
}
|
4.处理回调 再次强调这个类 微信回调的WXPayEntryActivity(这个类的名字不允许改变,包名固定为你应用的包名+wxapi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Override
public
void
onResp(BaseResp baseResp) {
Log.d(
"TAG"
,
"onPayFinish, errCode = "
+ baseResp.errCode);
//Log.e("TAG", "info=" + baseResp.errStr + ",transaction=" + baseResp.transaction + ",openId=" + baseResp.openId);
Bundle bundle =
new
Bundle();
if
(baseResp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
if
(baseResp.errCode ==
0
){
Toast.makeText(WXPayEntryActivity.
this
,
"支付成功"
, Toast.LENGTH_SHORT).show();
}
else
{
Toast.makeText(WXPayEntryActivity.
this
,
"支付失败"
, Toast.LENGTH_SHORT).show();
}
}
|
最后再强调一个,微信sdk想要成功调起,必须用正式签名,正式签名,正式签名。不只是支付sdk,分享和登录的也是一样
大体的流程就这样了,小弟能力有限,如果有什么错误欢迎指正!