JAVA实现微信支付
2017-11-21 16:39:04

前言

前段时间在做一个微信公众号,需要用到微信支付的接口,第一次用,果然和前面写其他接口经历一样,好繁琐的操作。去找了相关资料,看了看文档,越看越复杂,大多数博客上面的总是缺着一部分跑不起来,官方demo又好复杂,索性自己好好搞一次。

需求准备

1.首先一个公众号(服务号),也可以企业号,订阅号没有支付权限(权限可以查文档),可以去微信公众平台申请,而且需要认证,一年300
2.一个域名,必须经过ICP备案,必须备案,很重要,也就是说还需要一个服务器,备案大概1个月
3.微信支付开发文档
4.商户平台账号,也就是微信支付平台,前段时间微信支付单独迁移到了商户平台,所以需要单独申请

实现过程

1.前提公众号的配置已经完成,比如在公众平台的服务器配置,接口域名配置,授权配置等等。还有商户平台的支付授权目录填写。这些都可以通过文档或者搜索引擎找到。
2.接下来需要调用统一下单接口获取预支付id(prepay_id)
其中需要很多参数,参数的具体含义可见文档
这是签名生成算法大多数错误都是由于签名错误

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
//微信支付
String nonceStr = "2D8264ILTKCH16CQ2502SI8ZNMTM67VS";//随机字符串,可以自己生成
String MCHID = "11111111"; //商户号,商户平台注册
String WX_APPID = ; //APPID,也就是公众平台APPID
String body = "xx"; //商品描述
String WX_PAY_CALLBACK = "";//异步接收微信支付结果通知的回调地址
String ip = request.getRemoteAddr(); //用户端ip
String orderSn = String.valueOf((int)(new Date().getTime()));//商户订单号,这用的是时间
String relAmount = 1;//订单总金额,单位为分
String openid = user.getOpenid(); //用户openid
String key = ""; //商户平台API秘钥
// 加密,这里只列举必填字段
Map<String, String> map = new HashMap<String, String>();
map.put("body", body);//商品描述
map.put("mch_id", MCHID);//商户平台id
map.put("appid", WX_APPID);//公众号id
map.put("nonce_str", nonceStr);//随机字符串
map.put("notify_url", WX_PAY_CALLBACK);//异步回调api
map.put("spbill_create_ip", ip);//支付ip
map.put("out_trade_no", orderSn);//商品订单号
map.put("total_fee", relAmount);//真实金额
map.put("trade_type", "JSAPI");//JSAPI、h5调用
map.put("openid", openid);//支付用户openid
String paySign = "";
try {
paySign = WeixinUtil.getPayCustomSign(map,key);//WeixinUtil类会在下面展示
//这一句是签名生成算法
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
map.put("sign",paySign);

System.out.println("sign " + paySign);
String xml = "<xml>" +
"<appid>"+ WX_APPID +"</appid>"+
"<body>"+ body +"</body>"+
"<mch_id>"+ MCHID +"</mch_id>"+
"<nonce_str>"+ nonceStr +"</nonce_str>"+
"<notify_url>"+ WX_PAY_CALLBACK +"</notify_url>"+
"<openid>"+ openid +"</openid>"+
"<out_trade_no>"+ orderSn +"</out_trade_no>"+
"<spbill_create_ip>"+ ip +"</spbill_create_ip>"+
"<total_fee>"+ relAmount + "" +"</total_fee>"+
"<trade_type>JSAPI</trade_type>"+
"<sign>"+ paySign +"</sign>"+
"</xml>";
System.out.println(xml);
//将map转为XML格式
//统一下单,这里不用改
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //统一下单接口
String xmlStr = WeixinUtil.post(url, xml); //post请求数据
System.out.println(xmlStr);
String prepayid = "";
if (xmlStr.indexOf("SUCCESS") != -1) {
Map<String, String> map2 = null;
try {
map2 = WeixinUtil.xmlToMap(xmlStr);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

prepayid = (String) map2.get("prepay_id"); //获取prepayid
System.out.println("prepay_id " + prepayid);

现在如果返回了prepay id,那么这一步就算完成,如果出现错误,一般都是签名出错,这时候就要好好看看有没有语法错误,或者签名算法有误。

3.第二次签名,然后传值到前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
long timestamp = System.currentTimeMillis() / 1000; //时间戳
Map<String, String> signMap = new HashMap<String, String>();
signMap.put("appId", WX_APPID);//appid
signMap.put("timeStamp", String.valueOf(timestamp));
signMap.put("nonceStr", nonceStr);
signMap.put("package", "prepay_id="+prepayid);
signMap.put("signType", "MD5");
String paySign2 = "";
try {
paySign2 = WeixinUtil.getPayCustomSign(signMap,key);
System.out.println(paySign2);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("timestamp" + timestamp);
//以下的数据需要传到前端,可以用自己的方法传值
request.setAttribute("appId", WX_APPID);
request.setAttribute("paytimestamp", String.valueOf(timestamp));
request.setAttribute("paynonceStr", nonceStr);
request.setAttribute("paypackage", "prepay_id="+prepayid);
request.setAttribute("paysignType","MD5");
request.setAttribute("paySign", paySign2);
//去到确认支付页面,返回页面方式不同,(例:pay.html页面)

4.H5调用微信支付接口 官方文档内含代码
只需把参数改为自己的,调用接口就可以完成支付

5.我用到的WeixinUtil类,只是写了用到的一部分,导入的包可能很多没用。。

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
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import net.sf.json.JSONObject;

public class WeixinUtil {


private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };


public static String post(String url,String outStr){
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
String res =null;
try {
httpPost.setEntity(new StringEntity(outStr, "utf-8"));
HttpResponse response = httpClient.execute(httpPost);
String result = EntityUtils.toString(response.getEntity(),"utf-8");
res = result;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return res;
}


/**
* 获取支付所需签名
*/
public static String getPayCustomSign(Map<String, String> bizObj,String key) throws Exception {
String bizString = FormatBizQueryParaMap(bizObj, false);
return sign(bizString, key);
}

//支付所需签名处调用此方法
public static String sign(String content, String key)
throws Exception{
String signStr = "";
signStr = content + "&key=" + key;
return MD5(signStr).toUpperCase();
}

//上一方法,MD5加密处理
public final static String MD5(String s) {
char hexDigits[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
try {
byte[] btInput = s.getBytes();
MessageDigest mdInst = MessageDigest.getInstance("MD5");
mdInst.update(btInput);
byte[] md = mdInst.digest();
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 字典排序
*/
public static String FormatBizQueryParaMap(Map<String, String> paraMap,
boolean urlencode) throws Exception {
String buff = "";
try {
List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(paraMap.entrySet());
Collections.sort(infoIds,
new Comparator<Map.Entry<String, String>>() {
public int compare(Map.Entry<String, String> o1,
Map.Entry<String, String> o2) {
return (o1.getKey()).toString().compareTo(
o2.getKey());
}
});
for (int i = 0; i < infoIds.size(); i++) {
Map.Entry<String, String> item = infoIds.get(i);
//System.out.println(item.getKey());
if (item.getKey() != "") {
String key = item.getKey();
String val = item.getValue();
if (urlencode) {
val = URLEncoder.encode(val, "utf-8");
}
buff += key + "=" + val + "&";
}
}
if (buff.isEmpty() == false) {
buff = buff.substring(0, buff.length() - 1);
}
} catch (Exception e) {
throw new Exception(e.getMessage());
}
return buff;
}

public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
//WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}

}
}

总结

官方文档写的比较难懂,其他的又比较杂,有的不能用,当然也可以github找demo,这也是我第一次做这些,写的很难看不要在意,如果有错的地方希望提出来,欢迎指导交流。