Java 微信支付

微信支付开发文档 (V2版)

详细请求参数与返回参数请参考:
微信支付接口文档

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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
java复制代码/**
* <p>
* 便于使用,将所有的工具方法都集中在此,包含:
* 1. 执行 HTTP POST 请求,返回执行结果的 String
* 2. 创建签名(为下单数据创建)
* 3. 创建签名(为 APP 创建)
* 4. 检验签名
* 5. 读取 HTTP Request 内容
* 6. 读取 HTTP Response 内容
* 7. 将 Map 转化为 Xml
* 8. 将 Xml 转化为 Map
* 9. 生成 32 位随机字符串
* 10. MD5 签名
*/
public class WxUtils {

//APPID 微信开放平台 appid
public static final String APPID = "xxx";

//MCH_ID 微信商户平台 商户 mch_id
public static final String MCH_ID = "xxx";

//NOTIFY_URL 回调通知地址(接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。)
public static final String NOTIFY_URL = "xxx";

//API_KEY 微信商户平台密钥
public static String API_KEY = "xxx";

// 下单 API 地址
public static final String PLACE_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";


/**
* 1.请求方式
* @param requestUrl
* @param outputStr
* @return
*/
public static String httpsRequest(String requestUrl,String outputStr) {
try {
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();

conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod("POST");
//conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
conn.setRequestProperty("content-type", "text/xml;charset=utf-8");
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuilder buffer = new StringBuilder();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
conn.disconnect();
return buffer.toString();
} catch (ConnectException ce) {
System.out.println("连接超时:{}"+ ce);
} catch (Exception e) {
System.out.println("https请求异常:{}"+ e);
}
return null;
}

/** 2
* 第一次签名
*
* @param parameters 数据为服务器生成,下单时必须的字段排序签名
* @param key
* @return
*/
public static String createSign(SortedMap<String, Object> parameters, String key) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + key);
return encodeMD5(sb.toString());
}

/** 3
* 第二次签名
*
* @param result 数据为微信返回给服务器的数据(XML 的 String),再次签名后传回给客户端(APP)使用
* @param key 密钥
* @return
* @throws IOException
*/
public static Map createSign2(String result, String key) throws IOException {
SortedMap<String, Object> map = new TreeMap<>(transferXmlToMap(result));
String returnCode = (String)map.get("return_code");
if(StringUtils.equalsIgnoreCase("success",returnCode)){
String resultCode = (String)map.get("result_code");
Map app = new HashMap();
app.put("appid", map.get("appid"));//应用ID
app.put("partnerid", map.get("mch_id"));//商户号
app.put("prepayid", map.get("prepay_id"));//预支付交易会话ID
app.put("package", "Sign=WXPay");// 固定字段,保留,不可修改
app.put("noncestr", map.get("nonce_str"));//随机字符串
app.put("timestamp", new Date().getTime() / 1000); //时间戳 时间为秒,JDK 生成的是毫秒,故除以 1000
app.put("sign", createSign(new TreeMap<>(app), key));//签名
if(StringUtils.equalsIgnoreCase("success",resultCode)){
app.put("tradestate",map.get("trade_state"));
}
System.out.println(app+"-------------");
return app;
}
return null;
}

/** 4
* 验证签名是否正确
*
* @return boolean
* @throws Exception
*/
public static boolean checkSign(SortedMap<String, Object> parameters, String key) throws Exception {
String signWx = parameters.get("sign").toString();
if (signWx == null) return false;
parameters.remove("sign"); // 需要去掉原 map 中包含的 sign 字段再进行签名
String signMe = createSign(parameters, key);
return signWx.equals(signMe);
}

/** 5
* 读取 request body 内容作为字符串
*
* @param request
* @return
* @throws IOException
*/
public static String readRequest(HttpServletRequest request) throws IOException {
InputStream inputStream;
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream();
String str;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((str = in.readLine()) != null) {
sb.append(str);
}
in.close();
inputStream.close();
return sb.toString();
}

/** 6
* 读取 response body 内容为字符串
*/
public static String readResponse(HttpResponse response) throws IOException {
BufferedReader in = new BufferedReader(
new InputStreamReader(response.getEntity().getContent()));
String result = new String();
String line;
while ((line = in.readLine()) != null) {
result += line;
}
return result;
}

/** 7
* 将 Map 转化为 XML
*
* @param map
* @return
*/
public static String transferMapToXml(SortedMap<String, Object> map) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
for (String key : map.keySet()) {
sb.append("<").append(key).append(">")
.append(map.get(key))
.append("</").append(key).append(">");
}
return sb.append("</xml>").toString();
}


/** 8
* 将 XML 转化为 map
*
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static Map transferXmlToMap(String strxml) throws IOException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if (null == strxml || "".equals(strxml)) {
return null;
}
Map m = new HashMap();
InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder builder = new SAXBuilder();
Document doc = null;
try {
doc = builder.build(in);
} catch (JDOMException e) {
throw new IOException(e.getMessage()); // 统一转化为 IO 异常输出
}
// 解析 DOM
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v = "";
List children = e.getChildren();
if (children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = getChildrenText(children);
}
m.put(k, v);
}
//关闭流
in.close();
return m;
}

/** 9
* 辅助 transferXmlToMap 方法递归提取子节点数据
* @param children
* @return
*/
private static String getChildrenText(List<Element> children) {
StringBuffer sb = new StringBuffer();
if (!children.isEmpty()) {
Iterator<Element> it = children.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List<Element> list = e.getChildren();
sb.append("<" + name + ">");
if (!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}


/** 10
* 生成 32 位随机字符串,包含:数字、字母大小写
*
* @return
*/
public static String gen32RandomString() {
char[] dict = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
StringBuffer sb = new StringBuffer();
Random random = new Random();
for (int i = 0; i < 32; i++) {
sb.append(String.valueOf(dict[(int) (Math.random() * 62)]));
}
return sb.toString().toUpperCase();
}


/** 11
* MD5 签名
*
* @param str
* @return 签名后的字符串信息
*/
public static String encodeMD5(String str) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] inputByteArray = (str).getBytes();
messageDigest.update(inputByteArray);
byte[] resultByteArray = messageDigest.digest();
return byteArrayToHex(resultByteArray);
} catch (NoSuchAlgorithmException e) {
return null;
}
}

/** 12
* 辅助 encodeMD5 方法实现
* @param byteArray
* @return
*/
private static String byteArrayToHex(byte[] byteArray) {
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] resultCharArray = new char[byteArray.length * 2];
int index = 0;
for (byte b : byteArray) {
resultCharArray[index++] = hexDigits[b >>> 4 & 0xf];
resultCharArray[index++] = hexDigits[b & 0xf];
}
// 字符数组组合成字符串返回
return new String(resultCharArray);
}
}
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
typescript复制代码package com.xp.service.order.impl;

@Service
public class WXPayServiceImpl implements WXPayService {

@Override
public Map pay(List<OrderInfo> list, Integer userId, List<UserCoupon>couponList,String ipAddress){
try {
SortedMap<String, Object> parameters = new TreeMap<>();
parameters.put("appid", WxUtils.APPID);//应用ID
parameters.put("body", "微信支付");//商品描述
parameters.put("mch_id", WxUtils.MCH_ID);//商户号
parameters.put("nonce_str", WxUtils.gen32RandomString());//随机字符串 不长于32位
parameters.put("notify_url", WxUtils.NOTIFY_URL);//通知地址
//parameters.put("device_info", "WEB"); //设备号 默认"WEB"
parameters.put("out_trade_no", "唯一订单号");//商户订单号
parameters.put("spbill_create_ip", ipAddress);//终端IP
parameters.put("total_fee", "0.01"); // 总金额
parameters.put("trade_type", "APP");//交易类型
parameters.put("sign", WxUtils.createSign(parameters,WxUtils. API_KEY)); //签名 sign 必须在最后
String requestXML = WxUtils.transferMapToXml(parameters);
String result = WxUtils.httpsRequest(WxUtils.PLACE_URL, requestXML); // 执行 HTTP 请求,获取接收的字
return WxUtils.createSign2(result, WxUtils.API_KEY);
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

@Override
public String notify(HttpServletRequest request, HttpServletResponse response){
try {
// 预先设定返回的 response 类型为 xml
response.setHeader("Content-type", "application/xml");
// 读取参数,解析Xml为map
Map<String, String> map = WxUtils.transferXmlToMap(WxUtils.readRequest(request));
// 转换为有序 map,判断签名是否正确
boolean isSignSuccess = WxUtils.checkSign(new TreeMap<String, Object>(map), WxUtils.API_KEY);
if (isSignSuccess) {
// 签名校验成功,说明是微信服务器发出的数据
//String orderId = map.get("out_trade_no");
//if (tradeService.hasProcessed(orderId)) // 判断该订单是否已经被接收处理过
//return success();
// 可在此持久化微信传回的该 map 数据
if (map.get("return_code").equals("SUCCESS")) {
if (map.get("result_code").equals("SUCCESS")) {
/**
*回调成功之后处理业务逻辑(比如,更新订单状态,增加积分,通知仓库发货等等。)
*/

return "SUCCESS";
} else {
return "FAIL";
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "FAIL";
}
}

现在微信支付已经出现了 3.0 的文档版本,实现起来更加简单方便。微信支付开发文档 3.0 全新发布

但是,以上 v2 版的依然可用,已经亲测试过,完全 OK。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%