瀏覽代碼

微信支付

傅行帆 6 年之前
父節點
當前提交
590834028f

+ 72
- 0
CODE/smart-community/app-api/src/main/java/com/community/huiju/common/wxpay/HfWxConfig.java 查看文件

@@ -0,0 +1,72 @@
1
+package com.community.huiju.common.wxpay;
2
+
3
+import java.io.InputStream;
4
+
5
+/**
6
+ * 监控 控制器
7
+ * @author fxf
8
+ */
9
+public class HfWxConfig extends WXPayConfig {
10
+	
11
+	/**
12
+	 * 获取 App ID
13
+	 *
14
+	 * @return App ID
15
+	 */
16
+	@Override
17
+	public String getAppID() {
18
+		//银城慧家
19
+		return "wx6070fd3e8ef854e4";
20
+	}
21
+	
22
+	/**
23
+	 * 获取 Mch ID
24
+	 *
25
+	 * @return Mch ID
26
+	 */
27
+	@Override
28
+	public String getMchID() {
29
+		return "1525909671";
30
+	}
31
+	
32
+	/**
33
+	 * 获取 API 密钥
34
+	 *
35
+	 * @return API密钥
36
+	 */
37
+	@Override
38
+	public String getKey() {
39
+		return "HUIJU999999999999999999999999999";
40
+	}
41
+	
42
+	/**
43
+	 * 获取商户证书内容
44
+	 *
45
+	 * @return 商户证书内容
46
+	 */
47
+	@Override
48
+	public InputStream getCertStream() {
49
+		return null;
50
+	}
51
+	
52
+	/**
53
+	 * 获取WXPayDomain, 用于多域名容灾自动切换
54
+	 *
55
+	 * @return
56
+	 */
57
+	@Override
58
+	IWXPayDomain getWXPayDomain() {
59
+		IWXPayDomain iwxPayDomain = new IWXPayDomain() {
60
+			@Override
61
+			public void report(String domain, long elapsedTimeMillis, Exception ex) {
62
+			
63
+			}
64
+			@Override
65
+			public DomainInfo getDomain(WXPayConfig config) {
66
+				return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
67
+			}
68
+		};
69
+		return iwxPayDomain;
70
+	}
71
+}
72
+

+ 42
- 0
CODE/smart-community/app-api/src/main/java/com/community/huiju/common/wxpay/IWXPayDomain.java 查看文件

@@ -0,0 +1,42 @@
1
+package com.community.huiju.common.wxpay;
2
+
3
+/**
4
+ * 域名管理,实现主备域名自动切换
5
+ */
6
+public abstract interface IWXPayDomain {
7
+    /**
8
+     * 上报域名网络状况
9
+     * @param domain 域名。 比如:api.mch.weixin.qq.com
10
+     * @param elapsedTimeMillis 耗时
11
+     * @param ex 网络请求中出现的异常。
12
+     *           null表示没有异常
13
+     *           ConnectTimeoutException,表示建立网络连接异常
14
+     *           UnknownHostException, 表示dns解析异常
15
+     */
16
+    abstract void report(final String domain, long elapsedTimeMillis, final Exception ex);
17
+
18
+    /**
19
+     * 获取域名
20
+     * @param config 配置
21
+     * @return 域名
22
+     */
23
+    abstract DomainInfo getDomain(final WXPayConfig config);
24
+
25
+    static class DomainInfo{
26
+        public String domain;       //域名
27
+        public boolean primaryDomain;     //该域名是否为主域名。例如:api.mch.weixin.qq.com为主域名
28
+        public DomainInfo(String domain, boolean primaryDomain) {
29
+            this.domain = domain;
30
+            this.primaryDomain = primaryDomain;
31
+        }
32
+
33
+        @Override
34
+        public String toString() {
35
+            return "DomainInfo{" +
36
+                    "domain='" + domain + '\'' +
37
+                    ", primaryDomain=" + primaryDomain +
38
+                    '}';
39
+        }
40
+    }
41
+
42
+}

+ 689
- 0
CODE/smart-community/app-api/src/main/java/com/community/huiju/common/wxpay/WXPay.java 查看文件

@@ -0,0 +1,689 @@
1
+package com.community.huiju.common.wxpay;
2
+
3
+import com.community.huiju.common.wxpay.WXPayConstants.SignType;
4
+
5
+import java.util.HashMap;
6
+import java.util.Map;
7
+
8
+public class WXPay {
9
+
10
+    private WXPayConfig config;
11
+    private SignType signType;
12
+    private boolean autoReport;
13
+    private boolean useSandbox;
14
+    private String notifyUrl;
15
+    private WXPayRequest wxPayRequest;
16
+
17
+    public WXPay(final WXPayConfig config) throws Exception {
18
+        this(config, null, true, false);
19
+    }
20
+
21
+    public WXPay(final WXPayConfig config, final boolean autoReport) throws Exception {
22
+        this(config, null, autoReport, false);
23
+    }
24
+
25
+
26
+    public WXPay(final WXPayConfig config, final boolean autoReport, final boolean useSandbox) throws Exception{
27
+        this(config, null, autoReport, useSandbox);
28
+    }
29
+
30
+    public WXPay(final WXPayConfig config, final String notifyUrl) throws Exception {
31
+        this(config, notifyUrl, true, false);
32
+    }
33
+
34
+    public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport) throws Exception {
35
+        this(config, notifyUrl, autoReport, false);
36
+    }
37
+
38
+    public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {
39
+        this.config = config;
40
+        this.notifyUrl = notifyUrl;
41
+        this.autoReport = autoReport;
42
+        this.useSandbox = useSandbox;
43
+        if (useSandbox) {
44
+            this.signType = SignType.MD5; // 沙箱环境unifiedOrder
45
+        }
46
+        else {
47
+            this.signType = SignType.HMACSHA256;
48
+        }
49
+        this.wxPayRequest = new WXPayRequest(config);
50
+    }
51
+
52
+    private void checkWXPayConfig() throws Exception {
53
+        if (this.config == null) {
54
+            throw new Exception("config is null");
55
+        }
56
+        if (this.config.getAppID() == null || this.config.getAppID().trim().length() == 0) {
57
+            throw new Exception("appid in config is empty");
58
+        }
59
+        if (this.config.getMchID() == null || this.config.getMchID().trim().length() == 0) {
60
+            throw new Exception("appid in config is empty");
61
+        }
62
+        if (this.config.getCertStream() == null) {
63
+            throw new Exception("cert stream in config is empty");
64
+        }
65
+        if (this.config.getWXPayDomain() == null){
66
+            throw new Exception("config.getWXPayDomain() is null");
67
+        }
68
+
69
+        if (this.config.getHttpConnectTimeoutMs() < 10) {
70
+            throw new Exception("http connect timeout is too small");
71
+        }
72
+        if (this.config.getHttpReadTimeoutMs() < 10) {
73
+            throw new Exception("http read timeout is too small");
74
+        }
75
+
76
+    }
77
+
78
+    /**
79
+     * 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign <br>
80
+     * 该函数适用于商户适用于统一下单等接口,不适用于红包、代金券接口
81
+     *
82
+     * @param reqData
83
+     * @return
84
+     * @throws Exception
85
+     */
86
+    public Map<String, String> fillRequestData(Map<String, String> reqData) throws Exception {
87
+        reqData.put("appid", config.getAppID());
88
+        reqData.put("mch_id", config.getMchID());
89
+        reqData.put("nonce_str", WXPayUtil.generateNonceStr());
90
+        if (SignType.MD5.equals(this.signType)) {
91
+            reqData.put("sign_type", WXPayConstants.MD5);
92
+        }
93
+        else if (SignType.HMACSHA256.equals(this.signType)) {
94
+            reqData.put("sign_type", WXPayConstants.HMACSHA256);
95
+        }
96
+        reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType));
97
+        return reqData;
98
+    }
99
+
100
+    /**
101
+     * 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
102
+     *
103
+     * @param reqData 向wxpay post的请求数据
104
+     * @return 签名是否有效
105
+     * @throws Exception
106
+     */
107
+    public boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception {
108
+        // 返回数据的签名方式和请求中给定的签名方式是一致的
109
+        return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), this.signType);
110
+    }
111
+
112
+    /**
113
+     * 判断支付结果通知中的sign是否有效
114
+     *
115
+     * @param reqData 向wxpay post的请求数据
116
+     * @return 签名是否有效
117
+     * @throws Exception
118
+     */
119
+    public boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception {
120
+        String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE);
121
+        SignType signType;
122
+        if (signTypeInData == null) {
123
+            signType = SignType.MD5;
124
+        }
125
+        else {
126
+            signTypeInData = signTypeInData.trim();
127
+            if (signTypeInData.length() == 0) {
128
+                signType = SignType.MD5;
129
+            }
130
+            else if (WXPayConstants.MD5.equals(signTypeInData)) {
131
+                signType = SignType.MD5;
132
+            }
133
+            else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) {
134
+                signType = SignType.HMACSHA256;
135
+            }
136
+            else {
137
+                throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData));
138
+            }
139
+        }
140
+        return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), signType);
141
+    }
142
+
143
+
144
+    /**
145
+     * 不需要证书的请求
146
+     * @param urlSuffix String
147
+     * @param reqData 向wxpay post的请求数据
148
+     * @param connectTimeoutMs 超时时间,单位是毫秒
149
+     * @param readTimeoutMs 超时时间,单位是毫秒
150
+     * @return API返回数据
151
+     * @throws Exception
152
+     */
153
+    public String requestWithoutCert(String urlSuffix, Map<String, String> reqData,
154
+                                     int connectTimeoutMs, int readTimeoutMs) throws Exception {
155
+        String msgUUID = reqData.get("nonce_str");
156
+        String reqBody = WXPayUtil.mapToXml(reqData);
157
+
158
+        String resp = this.wxPayRequest.requestWithoutCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, autoReport);
159
+        return resp;
160
+    }
161
+
162
+
163
+    /**
164
+     * 需要证书的请求
165
+     * @param urlSuffix String
166
+     * @param reqData 向wxpay post的请求数据  Map
167
+     * @param connectTimeoutMs 超时时间,单位是毫秒
168
+     * @param readTimeoutMs 超时时间,单位是毫秒
169
+     * @return API返回数据
170
+     * @throws Exception
171
+     */
172
+    public String requestWithCert(String urlSuffix, Map<String, String> reqData,
173
+                                  int connectTimeoutMs, int readTimeoutMs) throws Exception {
174
+        String msgUUID= reqData.get("nonce_str");
175
+        String reqBody = WXPayUtil.mapToXml(reqData);
176
+
177
+        String resp = this.wxPayRequest.requestWithCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, this.autoReport);
178
+        return resp;
179
+    }
180
+
181
+    /**
182
+     * 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
183
+     * @param xmlStr API返回的XML格式数据
184
+     * @return Map类型数据
185
+     * @throws Exception
186
+     */
187
+    public Map<String, String> processResponseXml(String xmlStr) throws Exception {
188
+        String RETURN_CODE = "return_code";
189
+        String return_code;
190
+        Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
191
+        if (respData.containsKey(RETURN_CODE)) {
192
+            return_code = respData.get(RETURN_CODE);
193
+        }
194
+        else {
195
+            throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
196
+        }
197
+
198
+        if (return_code.equals(WXPayConstants.FAIL)) {
199
+            return respData;
200
+        }
201
+        else if (return_code.equals(WXPayConstants.SUCCESS)) {
202
+           if (this.isResponseSignatureValid(respData)) {
203
+               return respData;
204
+           }
205
+           else {
206
+               throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
207
+           }
208
+        }
209
+        else {
210
+            throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
211
+        }
212
+    }
213
+
214
+    /**
215
+     * 作用:提交刷卡支付<br>
216
+     * 场景:刷卡支付
217
+     * @param reqData 向wxpay post的请求数据
218
+     * @return API返回数据
219
+     * @throws Exception
220
+     */
221
+    public Map<String, String> microPay(Map<String, String> reqData) throws Exception {
222
+        return this.microPay(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
223
+    }
224
+
225
+
226
+    /**
227
+     * 作用:提交刷卡支付<br>
228
+     * 场景:刷卡支付
229
+     * @param reqData 向wxpay post的请求数据
230
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
231
+     * @param readTimeoutMs 读超时时间,单位是毫秒
232
+     * @return API返回数据
233
+     * @throws Exception
234
+     */
235
+    public Map<String, String> microPay(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
236
+        String url;
237
+        if (this.useSandbox) {
238
+            url = WXPayConstants.SANDBOX_MICROPAY_URL_SUFFIX;
239
+        }
240
+        else {
241
+            url = WXPayConstants.MICROPAY_URL_SUFFIX;
242
+        }
243
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
244
+        return this.processResponseXml(respXml);
245
+    }
246
+
247
+    /**
248
+     * 提交刷卡支付,针对软POS,尽可能做成功
249
+     * 内置重试机制,最多60s
250
+     * @param reqData
251
+     * @return
252
+     * @throws Exception
253
+     */
254
+    public Map<String, String> microPayWithPos(Map<String, String> reqData) throws Exception {
255
+        return this.microPayWithPos(reqData, this.config.getHttpConnectTimeoutMs());
256
+    }
257
+
258
+    /**
259
+     * 提交刷卡支付,针对软POS,尽可能做成功
260
+     * 内置重试机制,最多60s
261
+     * @param reqData
262
+     * @param connectTimeoutMs
263
+     * @return
264
+     * @throws Exception
265
+     */
266
+    public Map<String, String> microPayWithPos(Map<String, String> reqData, int connectTimeoutMs) throws Exception {
267
+        int remainingTimeMs = 60*1000;
268
+        long startTimestampMs = 0;
269
+        Map<String, String> lastResult = null;
270
+        Exception lastException = null;
271
+
272
+        while (true) {
273
+            startTimestampMs = WXPayUtil.getCurrentTimestampMs();
274
+            int readTimeoutMs = remainingTimeMs - connectTimeoutMs;
275
+            if (readTimeoutMs > 1000) {
276
+                try {
277
+                    lastResult = this.microPay(reqData, connectTimeoutMs, readTimeoutMs);
278
+                    String returnCode = lastResult.get("return_code");
279
+                    if (returnCode.equals("SUCCESS")) {
280
+                        String resultCode = lastResult.get("result_code");
281
+                        String errCode = lastResult.get("err_code");
282
+                        if (resultCode.equals("SUCCESS")) {
283
+                            break;
284
+                        }
285
+                        else {
286
+                            // 看错误码,若支付结果未知,则重试提交刷卡支付
287
+                            if (errCode.equals("SYSTEMERROR") || errCode.equals("BANKERROR") || errCode.equals("USERPAYING")) {
288
+                                remainingTimeMs = remainingTimeMs - (int)(WXPayUtil.getCurrentTimestampMs() - startTimestampMs);
289
+                                if (remainingTimeMs <= 100) {
290
+                                    break;
291
+                                }
292
+                                else {
293
+                                    WXPayUtil.getLogger().info("microPayWithPos: try micropay again");
294
+                                    if (remainingTimeMs > 5*1000) {
295
+                                        Thread.sleep(5*1000);
296
+                                    }
297
+                                    else {
298
+                                        Thread.sleep(1*1000);
299
+                                    }
300
+                                    continue;
301
+                                }
302
+                            }
303
+                            else {
304
+                                break;
305
+                            }
306
+                        }
307
+                    }
308
+                    else {
309
+                        break;
310
+                    }
311
+                }
312
+                catch (Exception ex) {
313
+                    lastResult = null;
314
+                    lastException = ex;
315
+                }
316
+            }
317
+            else {
318
+                break;
319
+            }
320
+        }
321
+
322
+        if (lastResult == null) {
323
+            throw lastException;
324
+        }
325
+        else {
326
+            return lastResult;
327
+        }
328
+    }
329
+
330
+
331
+
332
+    /**
333
+     * 作用:统一下单<br>
334
+     * 场景:公共号支付、扫码支付、APP支付
335
+     * @param reqData 向wxpay post的请求数据
336
+     * @return API返回数据
337
+     * @throws Exception
338
+     */
339
+    public Map<String, String> unifiedOrder(Map<String, String> reqData) throws Exception {
340
+        return this.unifiedOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
341
+    }
342
+
343
+
344
+    /**
345
+     * 作用:统一下单<br>
346
+     * 场景:公共号支付、扫码支付、APP支付
347
+     * @param reqData 向wxpay post的请求数据
348
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
349
+     * @param readTimeoutMs 读超时时间,单位是毫秒
350
+     * @return API返回数据
351
+     * @throws Exception
352
+     */
353
+    public Map<String, String> unifiedOrder(Map<String, String> reqData,  int connectTimeoutMs, int readTimeoutMs) throws Exception {
354
+        String url;
355
+        if (this.useSandbox) {
356
+            url = WXPayConstants.SANDBOX_UNIFIEDORDER_URL_SUFFIX;
357
+        }
358
+        else {
359
+            url = WXPayConstants.UNIFIEDORDER_URL_SUFFIX;
360
+        }
361
+        if(this.notifyUrl != null) {
362
+            reqData.put("notify_url", this.notifyUrl);
363
+        }
364
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
365
+        return this.processResponseXml(respXml);
366
+    }
367
+
368
+
369
+    /**
370
+     * 作用:查询订单<br>
371
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
372
+     * @param reqData 向wxpay post的请求数据
373
+     * @return API返回数据
374
+     * @throws Exception
375
+     */
376
+    public Map<String, String> orderQuery(Map<String, String> reqData) throws Exception {
377
+        return this.orderQuery(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
378
+    }
379
+
380
+
381
+    /**
382
+     * 作用:查询订单<br>
383
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
384
+     * @param reqData 向wxpay post的请求数据 int
385
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
386
+     * @param readTimeoutMs 读超时时间,单位是毫秒
387
+     * @return API返回数据
388
+     * @throws Exception
389
+     */
390
+    public Map<String, String> orderQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
391
+        String url;
392
+        if (this.useSandbox) {
393
+            url = WXPayConstants.SANDBOX_ORDERQUERY_URL_SUFFIX;
394
+        }
395
+        else {
396
+            url = WXPayConstants.ORDERQUERY_URL_SUFFIX;
397
+        }
398
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
399
+        return this.processResponseXml(respXml);
400
+    }
401
+
402
+
403
+    /**
404
+     * 作用:撤销订单<br>
405
+     * 场景:刷卡支付
406
+     * @param reqData 向wxpay post的请求数据
407
+     * @return API返回数据
408
+     * @throws Exception
409
+     */
410
+    public Map<String, String> reverse(Map<String, String> reqData) throws Exception {
411
+        return this.reverse(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
412
+    }
413
+
414
+
415
+    /**
416
+     * 作用:撤销订单<br>
417
+     * 场景:刷卡支付<br>
418
+     * 其他:需要证书
419
+     * @param reqData 向wxpay post的请求数据
420
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
421
+     * @param readTimeoutMs 读超时时间,单位是毫秒
422
+     * @return API返回数据
423
+     * @throws Exception
424
+     */
425
+    public Map<String, String> reverse(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
426
+        String url;
427
+        if (this.useSandbox) {
428
+            url = WXPayConstants.SANDBOX_REVERSE_URL_SUFFIX;
429
+        }
430
+        else {
431
+            url = WXPayConstants.REVERSE_URL_SUFFIX;
432
+        }
433
+        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
434
+        return this.processResponseXml(respXml);
435
+    }
436
+
437
+
438
+    /**
439
+     * 作用:关闭订单<br>
440
+     * 场景:公共号支付、扫码支付、APP支付
441
+     * @param reqData 向wxpay post的请求数据
442
+     * @return API返回数据
443
+     * @throws Exception
444
+     */
445
+    public Map<String, String> closeOrder(Map<String, String> reqData) throws Exception {
446
+        return this.closeOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
447
+    }
448
+
449
+
450
+    /**
451
+     * 作用:关闭订单<br>
452
+     * 场景:公共号支付、扫码支付、APP支付
453
+     * @param reqData 向wxpay post的请求数据
454
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
455
+     * @param readTimeoutMs 读超时时间,单位是毫秒
456
+     * @return API返回数据
457
+     * @throws Exception
458
+     */
459
+    public Map<String, String> closeOrder(Map<String, String> reqData,  int connectTimeoutMs, int readTimeoutMs) throws Exception {
460
+        String url;
461
+        if (this.useSandbox) {
462
+            url = WXPayConstants.SANDBOX_CLOSEORDER_URL_SUFFIX;
463
+        }
464
+        else {
465
+            url = WXPayConstants.CLOSEORDER_URL_SUFFIX;
466
+        }
467
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
468
+        return this.processResponseXml(respXml);
469
+    }
470
+
471
+
472
+    /**
473
+     * 作用:申请退款<br>
474
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
475
+     * @param reqData 向wxpay post的请求数据
476
+     * @return API返回数据
477
+     * @throws Exception
478
+     */
479
+    public Map<String, String> refund(Map<String, String> reqData) throws Exception {
480
+        return this.refund(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
481
+    }
482
+
483
+
484
+    /**
485
+     * 作用:申请退款<br>
486
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>
487
+     * 其他:需要证书
488
+     * @param reqData 向wxpay post的请求数据
489
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
490
+     * @param readTimeoutMs 读超时时间,单位是毫秒
491
+     * @return API返回数据
492
+     * @throws Exception
493
+     */
494
+    public Map<String, String> refund(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
495
+        String url;
496
+        if (this.useSandbox) {
497
+            url = WXPayConstants.SANDBOX_REFUND_URL_SUFFIX;
498
+        }
499
+        else {
500
+            url = WXPayConstants.REFUND_URL_SUFFIX;
501
+        }
502
+        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
503
+        return this.processResponseXml(respXml);
504
+    }
505
+
506
+
507
+    /**
508
+     * 作用:退款查询<br>
509
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
510
+     * @param reqData 向wxpay post的请求数据
511
+     * @return API返回数据
512
+     * @throws Exception
513
+     */
514
+    public Map<String, String> refundQuery(Map<String, String> reqData) throws Exception {
515
+        return this.refundQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
516
+    }
517
+
518
+
519
+    /**
520
+     * 作用:退款查询<br>
521
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
522
+     * @param reqData 向wxpay post的请求数据
523
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
524
+     * @param readTimeoutMs 读超时时间,单位是毫秒
525
+     * @return API返回数据
526
+     * @throws Exception
527
+     */
528
+    public Map<String, String> refundQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
529
+        String url;
530
+        if (this.useSandbox) {
531
+            url = WXPayConstants.SANDBOX_REFUNDQUERY_URL_SUFFIX;
532
+        }
533
+        else {
534
+            url = WXPayConstants.REFUNDQUERY_URL_SUFFIX;
535
+        }
536
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
537
+        return this.processResponseXml(respXml);
538
+    }
539
+
540
+
541
+    /**
542
+     * 作用:对账单下载(成功时返回对账单数据,失败时返回XML格式数据)<br>
543
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
544
+     * @param reqData 向wxpay post的请求数据
545
+     * @return API返回数据
546
+     * @throws Exception
547
+     */
548
+    public Map<String, String> downloadBill(Map<String, String> reqData) throws Exception {
549
+        return this.downloadBill(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
550
+    }
551
+
552
+
553
+    /**
554
+     * 作用:对账单下载<br>
555
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>
556
+     * 其他:无论是否成功都返回Map。若成功,返回的Map中含有return_code、return_msg、data,
557
+     *      其中return_code为`SUCCESS`,data为对账单数据。
558
+     * @param reqData 向wxpay post的请求数据
559
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
560
+     * @param readTimeoutMs 读超时时间,单位是毫秒
561
+     * @return 经过封装的API返回数据
562
+     * @throws Exception
563
+     */
564
+    public Map<String, String> downloadBill(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
565
+        String url;
566
+        if (this.useSandbox) {
567
+            url = WXPayConstants.SANDBOX_DOWNLOADBILL_URL_SUFFIX;
568
+        }
569
+        else {
570
+            url = WXPayConstants.DOWNLOADBILL_URL_SUFFIX;
571
+        }
572
+        String respStr = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs).trim();
573
+        Map<String, String> ret;
574
+        // 出现错误,返回XML数据
575
+        if (respStr.indexOf("<") == 0) {
576
+            ret = WXPayUtil.xmlToMap(respStr);
577
+        }
578
+        else {
579
+            // 正常返回csv数据
580
+            ret = new HashMap<String, String>();
581
+            ret.put("return_code", WXPayConstants.SUCCESS);
582
+            ret.put("return_msg", "ok");
583
+            ret.put("data", respStr);
584
+        }
585
+        return ret;
586
+    }
587
+
588
+
589
+    /**
590
+     * 作用:交易保障<br>
591
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
592
+     * @param reqData 向wxpay post的请求数据
593
+     * @return API返回数据
594
+     * @throws Exception
595
+     */
596
+    public Map<String, String> report(Map<String, String> reqData) throws Exception {
597
+        return this.report(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
598
+    }
599
+
600
+
601
+    /**
602
+     * 作用:交易保障<br>
603
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
604
+     * @param reqData 向wxpay post的请求数据
605
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
606
+     * @param readTimeoutMs 读超时时间,单位是毫秒
607
+     * @return API返回数据
608
+     * @throws Exception
609
+     */
610
+    public Map<String, String> report(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
611
+        String url;
612
+        if (this.useSandbox) {
613
+            url = WXPayConstants.SANDBOX_REPORT_URL_SUFFIX;
614
+        }
615
+        else {
616
+            url = WXPayConstants.REPORT_URL_SUFFIX;
617
+        }
618
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
619
+        return WXPayUtil.xmlToMap(respXml);
620
+    }
621
+
622
+
623
+    /**
624
+     * 作用:转换短链接<br>
625
+     * 场景:刷卡支付、扫码支付
626
+     * @param reqData 向wxpay post的请求数据
627
+     * @return API返回数据
628
+     * @throws Exception
629
+     */
630
+    public Map<String, String> shortUrl(Map<String, String> reqData) throws Exception {
631
+        return this.shortUrl(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
632
+    }
633
+
634
+
635
+    /**
636
+     * 作用:转换短链接<br>
637
+     * 场景:刷卡支付、扫码支付
638
+     * @param reqData 向wxpay post的请求数据
639
+     * @return API返回数据
640
+     * @throws Exception
641
+     */
642
+    public Map<String, String> shortUrl(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
643
+        String url;
644
+        if (this.useSandbox) {
645
+            url = WXPayConstants.SANDBOX_SHORTURL_URL_SUFFIX;
646
+        }
647
+        else {
648
+            url = WXPayConstants.SHORTURL_URL_SUFFIX;
649
+        }
650
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
651
+        return this.processResponseXml(respXml);
652
+    }
653
+
654
+
655
+    /**
656
+     * 作用:授权码查询OPENID接口<br>
657
+     * 场景:刷卡支付
658
+     * @param reqData 向wxpay post的请求数据
659
+     * @return API返回数据
660
+     * @throws Exception
661
+     */
662
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData) throws Exception {
663
+        return this.authCodeToOpenid(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
664
+    }
665
+
666
+
667
+    /**
668
+     * 作用:授权码查询OPENID接口<br>
669
+     * 场景:刷卡支付
670
+     * @param reqData 向wxpay post的请求数据
671
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
672
+     * @param readTimeoutMs 读超时时间,单位是毫秒
673
+     * @return API返回数据
674
+     * @throws Exception
675
+     */
676
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
677
+        String url;
678
+        if (this.useSandbox) {
679
+            url = WXPayConstants.SANDBOX_AUTHCODETOOPENID_URL_SUFFIX;
680
+        }
681
+        else {
682
+            url = WXPayConstants.AUTHCODETOOPENID_URL_SUFFIX;
683
+        }
684
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
685
+        return this.processResponseXml(respXml);
686
+    }
687
+
688
+
689
+} // end class

+ 103
- 0
CODE/smart-community/app-api/src/main/java/com/community/huiju/common/wxpay/WXPayConfig.java 查看文件

@@ -0,0 +1,103 @@
1
+package com.community.huiju.common.wxpay;
2
+
3
+import java.io.InputStream;
4
+
5
+public abstract class WXPayConfig {
6
+
7
+
8
+
9
+    /**
10
+     * 获取 App ID
11
+     *
12
+     * @return App ID
13
+     */
14
+    abstract String getAppID();
15
+
16
+
17
+    /**
18
+     * 获取 Mch ID
19
+     *
20
+     * @return Mch ID
21
+     */
22
+    abstract String getMchID();
23
+
24
+
25
+    /**
26
+     * 获取 API 密钥
27
+     *
28
+     * @return API密钥
29
+     */
30
+    abstract String getKey();
31
+
32
+
33
+    /**
34
+     * 获取商户证书内容
35
+     *
36
+     * @return 商户证书内容
37
+     */
38
+    abstract InputStream getCertStream();
39
+
40
+    /**
41
+     * HTTP(S) 连接超时时间,单位毫秒
42
+     *
43
+     * @return
44
+     */
45
+    public int getHttpConnectTimeoutMs() {
46
+        return 6*1000;
47
+    }
48
+
49
+    /**
50
+     * HTTP(S) 读数据超时时间,单位毫秒
51
+     *
52
+     * @return
53
+     */
54
+    public int getHttpReadTimeoutMs() {
55
+        return 8*1000;
56
+    }
57
+
58
+    /**
59
+     * 获取WXPayDomain, 用于多域名容灾自动切换
60
+     * @return
61
+     */
62
+    abstract IWXPayDomain getWXPayDomain();
63
+
64
+    /**
65
+     * 是否自动上报。
66
+     * 若要关闭自动上报,子类中实现该函数返回 false 即可。
67
+     *
68
+     * @return
69
+     */
70
+    public boolean shouldAutoReport() {
71
+        return true;
72
+    }
73
+
74
+    /**
75
+     * 进行健康上报的线程的数量
76
+     *
77
+     * @return
78
+     */
79
+    public int getReportWorkerNum() {
80
+        return 6;
81
+    }
82
+
83
+
84
+    /**
85
+     * 健康上报缓存消息的最大数量。会有线程去独立上报
86
+     * 粗略计算:加入一条消息200B,10000消息占用空间 2000 KB,约为2MB,可以接受
87
+     *
88
+     * @return
89
+     */
90
+    public int getReportQueueMaxSize() {
91
+        return 10000;
92
+    }
93
+
94
+    /**
95
+     * 批量上报,一次最多上报多个数据
96
+     *
97
+     * @return
98
+     */
99
+    public int getReportBatchSize() {
100
+        return 10;
101
+    }
102
+
103
+}

+ 59
- 0
CODE/smart-community/app-api/src/main/java/com/community/huiju/common/wxpay/WXPayConstants.java 查看文件

@@ -0,0 +1,59 @@
1
+package com.community.huiju.common.wxpay;
2
+
3
+import org.apache.http.client.HttpClient;
4
+
5
+/**
6
+ * 常量
7
+ */
8
+public class WXPayConstants {
9
+
10
+    public enum SignType {
11
+        MD5, HMACSHA256
12
+    }
13
+
14
+    public static final String DOMAIN_API = "api.mch.weixin.qq.com";
15
+    public static final String DOMAIN_API2 = "api2.mch.weixin.qq.com";
16
+    public static final String DOMAIN_APIHK = "apihk.mch.weixin.qq.com";
17
+    public static final String DOMAIN_APIUS = "apius.mch.weixin.qq.com";
18
+
19
+
20
+    public static final String FAIL     = "FAIL";
21
+    public static final String SUCCESS  = "SUCCESS";
22
+    public static final String HMACSHA256 = "HMAC-SHA256";
23
+    public static final String MD5 = "MD5";
24
+
25
+    public static final String FIELD_SIGN = "sign";
26
+    public static final String FIELD_SIGN_TYPE = "sign_type";
27
+
28
+    public static final String WXPAYSDK_VERSION = "WXPaySDK/3.0.9";
29
+    public static final String USER_AGENT = WXPAYSDK_VERSION +
30
+            " (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") +
31
+            ") Java/" + System.getProperty("java.version") + " HttpClient/" + HttpClient.class.getPackage().getImplementationVersion();
32
+
33
+    public static final String MICROPAY_URL_SUFFIX     = "/pay/micropay";
34
+    public static final String UNIFIEDORDER_URL_SUFFIX = "/pay/unifiedorder";
35
+    public static final String ORDERQUERY_URL_SUFFIX   = "/pay/orderquery";
36
+    public static final String REVERSE_URL_SUFFIX      = "/secapi/pay/reverse";
37
+    public static final String CLOSEORDER_URL_SUFFIX   = "/pay/closeorder";
38
+    public static final String REFUND_URL_SUFFIX       = "/secapi/pay/refund";
39
+    public static final String REFUNDQUERY_URL_SUFFIX  = "/pay/refundquery";
40
+    public static final String DOWNLOADBILL_URL_SUFFIX = "/pay/downloadbill";
41
+    public static final String REPORT_URL_SUFFIX       = "/payitil/report";
42
+    public static final String SHORTURL_URL_SUFFIX     = "/tools/shorturl";
43
+    public static final String AUTHCODETOOPENID_URL_SUFFIX = "/tools/authcodetoopenid";
44
+
45
+    // sandbox
46
+    public static final String SANDBOX_MICROPAY_URL_SUFFIX     = "/sandboxnew/pay/micropay";
47
+    public static final String SANDBOX_UNIFIEDORDER_URL_SUFFIX = "/sandboxnew/pay/unifiedorder";
48
+    public static final String SANDBOX_ORDERQUERY_URL_SUFFIX   = "/sandboxnew/pay/orderquery";
49
+    public static final String SANDBOX_REVERSE_URL_SUFFIX      = "/sandboxnew/secapi/pay/reverse";
50
+    public static final String SANDBOX_CLOSEORDER_URL_SUFFIX   = "/sandboxnew/pay/closeorder";
51
+    public static final String SANDBOX_REFUND_URL_SUFFIX       = "/sandboxnew/secapi/pay/refund";
52
+    public static final String SANDBOX_REFUNDQUERY_URL_SUFFIX  = "/sandboxnew/pay/refundquery";
53
+    public static final String SANDBOX_DOWNLOADBILL_URL_SUFFIX = "/sandboxnew/pay/downloadbill";
54
+    public static final String SANDBOX_REPORT_URL_SUFFIX       = "/sandboxnew/payitil/report";
55
+    public static final String SANDBOX_SHORTURL_URL_SUFFIX     = "/sandboxnew/tools/shorturl";
56
+    public static final String SANDBOX_AUTHCODETOOPENID_URL_SUFFIX = "/sandboxnew/tools/authcodetoopenid";
57
+
58
+}
59
+

+ 265
- 0
CODE/smart-community/app-api/src/main/java/com/community/huiju/common/wxpay/WXPayReport.java 查看文件

@@ -0,0 +1,265 @@
1
+package com.community.huiju.common.wxpay;
2
+
3
+import org.apache.http.HttpEntity;
4
+import org.apache.http.HttpResponse;
5
+import org.apache.http.client.HttpClient;
6
+import org.apache.http.client.config.RequestConfig;
7
+import org.apache.http.client.methods.HttpPost;
8
+import org.apache.http.config.RegistryBuilder;
9
+import org.apache.http.conn.socket.ConnectionSocketFactory;
10
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
11
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
12
+import org.apache.http.entity.StringEntity;
13
+import org.apache.http.impl.client.HttpClientBuilder;
14
+import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
15
+import org.apache.http.util.EntityUtils;
16
+
17
+import java.util.concurrent.ExecutorService;
18
+import java.util.concurrent.Executors;
19
+import java.util.concurrent.LinkedBlockingQueue;
20
+import java.util.concurrent.ThreadFactory;
21
+
22
+/**
23
+ * 交易保障
24
+ */
25
+public class WXPayReport {
26
+
27
+    public static class ReportInfo {
28
+
29
+        /**
30
+         * 布尔变量使用int。0为false, 1为true。
31
+         */
32
+
33
+        // 基本信息
34
+        private String version = "v1";
35
+        private String sdk = WXPayConstants.WXPAYSDK_VERSION;
36
+        private String uuid;  // 交易的标识
37
+        private long timestamp;   // 上报时的时间戳,单位秒
38
+        private long elapsedTimeMillis; // 耗时,单位 毫秒
39
+
40
+        // 针对主域名
41
+        private String firstDomain;  // 第1次请求的域名
42
+        private boolean primaryDomain; //是否主域名
43
+        private int firstConnectTimeoutMillis;  // 第1次请求设置的连接超时时间,单位 毫秒
44
+        private int firstReadTimeoutMillis;  // 第1次请求设置的读写超时时间,单位 毫秒
45
+        private int firstHasDnsError;  // 第1次请求是否出现dns问题
46
+        private int firstHasConnectTimeout; // 第1次请求是否出现连接超时
47
+        private int firstHasReadTimeout; // 第1次请求是否出现连接超时
48
+
49
+        public ReportInfo(String uuid, long timestamp, long elapsedTimeMillis, String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis, boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) {
50
+            this.uuid = uuid;
51
+            this.timestamp = timestamp;
52
+            this.elapsedTimeMillis = elapsedTimeMillis;
53
+            this.firstDomain = firstDomain;
54
+            this.primaryDomain = primaryDomain;
55
+            this.firstConnectTimeoutMillis = firstConnectTimeoutMillis;
56
+            this.firstReadTimeoutMillis = firstReadTimeoutMillis;
57
+            this.firstHasDnsError = firstHasDnsError?1:0;
58
+            this.firstHasConnectTimeout = firstHasConnectTimeout?1:0;
59
+            this.firstHasReadTimeout = firstHasReadTimeout?1:0;
60
+         }
61
+
62
+        @Override
63
+        public String toString() {
64
+            return "ReportInfo{" +
65
+                    "version='" + version + '\'' +
66
+                    ", sdk='" + sdk + '\'' +
67
+                    ", uuid='" + uuid + '\'' +
68
+                    ", timestamp=" + timestamp +
69
+                    ", elapsedTimeMillis=" + elapsedTimeMillis +
70
+                    ", firstDomain='" + firstDomain + '\'' +
71
+                    ", primaryDomain=" + primaryDomain +
72
+                    ", firstConnectTimeoutMillis=" + firstConnectTimeoutMillis +
73
+                    ", firstReadTimeoutMillis=" + firstReadTimeoutMillis +
74
+                    ", firstHasDnsError=" + firstHasDnsError +
75
+                    ", firstHasConnectTimeout=" + firstHasConnectTimeout +
76
+                    ", firstHasReadTimeout=" + firstHasReadTimeout +
77
+                    '}';
78
+        }
79
+
80
+        /**
81
+         * 转换成 csv 格式
82
+         *
83
+         * @return
84
+         */
85
+        public String toLineString(String key) {
86
+            String separator = ",";
87
+            Object[] objects = new Object[] {
88
+                version, sdk, uuid, timestamp, elapsedTimeMillis,
89
+                    firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,
90
+                    firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout
91
+            };
92
+            StringBuffer sb = new StringBuffer();
93
+            for(Object obj: objects) {
94
+                sb.append(obj).append(separator);
95
+            }
96
+            try {
97
+                String sign = WXPayUtil.HMACSHA256(sb.toString(), key);
98
+                sb.append(sign);
99
+                return sb.toString();
100
+            }
101
+            catch (Exception ex) {
102
+                return null;
103
+            }
104
+
105
+        }
106
+
107
+    }
108
+
109
+    private static final String REPORT_URL = "http://report.mch.weixin.qq.com/wxpay/report/default";
110
+    // private static final String REPORT_URL = "http://127.0.0.1:5000/test";
111
+
112
+
113
+    private static final int DEFAULT_CONNECT_TIMEOUT_MS = 6*1000;
114
+    private static final int DEFAULT_READ_TIMEOUT_MS = 8*1000;
115
+
116
+    private LinkedBlockingQueue<String> reportMsgQueue = null;
117
+    private WXPayConfig config;
118
+    private ExecutorService executorService;
119
+
120
+    private volatile static WXPayReport INSTANCE;
121
+
122
+    private WXPayReport(final WXPayConfig config) {
123
+        this.config = config;
124
+        reportMsgQueue = new LinkedBlockingQueue<String>(config.getReportQueueMaxSize());
125
+
126
+        // 添加处理线程
127
+        executorService = Executors.newFixedThreadPool(config.getReportWorkerNum(), new ThreadFactory() {
128
+            public Thread newThread(Runnable r) {
129
+                Thread t = Executors.defaultThreadFactory().newThread(r);
130
+                t.setDaemon(true);
131
+                return t;
132
+            }
133
+        });
134
+
135
+        if (config.shouldAutoReport()) {
136
+            WXPayUtil.getLogger().info("report worker num: {}", config.getReportWorkerNum());
137
+            for (int i = 0; i < config.getReportWorkerNum(); ++i) {
138
+                executorService.execute(new Runnable() {
139
+                    public void run() {
140
+                        while (true) {
141
+                            // 先用 take 获取数据
142
+                            try {
143
+                                StringBuffer sb = new StringBuffer();
144
+                                String firstMsg = reportMsgQueue.take();
145
+                                WXPayUtil.getLogger().info("get first report msg: {}", firstMsg);
146
+                                String msg = null;
147
+                                sb.append(firstMsg); //会阻塞至有消息
148
+                                int remainNum = config.getReportBatchSize() - 1;
149
+                                for (int j=0; j<remainNum; ++j) {
150
+                                    WXPayUtil.getLogger().info("try get remain report msg");
151
+                                    // msg = reportMsgQueue.poll();  // 不阻塞了
152
+                                    msg = reportMsgQueue.take();
153
+                                    WXPayUtil.getLogger().info("get remain report msg: {}", msg);
154
+                                    if (msg == null) {
155
+                                        break;
156
+                                    }
157
+                                    else {
158
+                                        sb.append("\n");
159
+                                        sb.append(msg);
160
+                                    }
161
+                                }
162
+                                // 上报
163
+                                WXPayReport.httpRequest(sb.toString(), DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
164
+                            }
165
+                            catch (Exception ex) {
166
+                                WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage());
167
+                            }
168
+                        }
169
+                    }
170
+                });
171
+            }
172
+        }
173
+
174
+    }
175
+
176
+    /**
177
+     * 单例,双重校验,请在 JDK 1.5及更高版本中使用
178
+     *
179
+     * @param config
180
+     * @return
181
+     */
182
+    public static WXPayReport getInstance(WXPayConfig config) {
183
+        if (INSTANCE == null) {
184
+            synchronized (WXPayReport.class) {
185
+                if (INSTANCE == null) {
186
+                    INSTANCE = new WXPayReport(config);
187
+                }
188
+            }
189
+        }
190
+        return INSTANCE;
191
+    }
192
+
193
+    public void report(String uuid, long elapsedTimeMillis,
194
+                       String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis,
195
+                       boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) {
196
+        long currentTimestamp = WXPayUtil.getCurrentTimestamp();
197
+        ReportInfo reportInfo = new ReportInfo(uuid, currentTimestamp, elapsedTimeMillis,
198
+                firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,
199
+                firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout);
200
+        String data = reportInfo.toLineString(config.getKey());
201
+        WXPayUtil.getLogger().info("report {}", data);
202
+        if (data != null) {
203
+            reportMsgQueue.offer(data);
204
+        }
205
+    }
206
+
207
+
208
+    @Deprecated
209
+    private void reportSync(final String data) throws Exception {
210
+        httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
211
+    }
212
+
213
+    @Deprecated
214
+    private void reportAsync(final String data) throws Exception {
215
+        new Thread(new Runnable() {
216
+            public void run() {
217
+                try {
218
+                    httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
219
+                }
220
+                catch (Exception ex) {
221
+                    WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage());
222
+                }
223
+            }
224
+        }).start();
225
+    }
226
+
227
+    /**
228
+     * http 请求
229
+     * @param data
230
+     * @param connectTimeoutMs
231
+     * @param readTimeoutMs
232
+     * @return
233
+     * @throws Exception
234
+     */
235
+    private static String httpRequest(String data, int connectTimeoutMs, int readTimeoutMs) throws Exception{
236
+        BasicHttpClientConnectionManager connManager;
237
+        connManager = new BasicHttpClientConnectionManager(
238
+                RegistryBuilder.<ConnectionSocketFactory>create()
239
+                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
240
+                        .register("https", SSLConnectionSocketFactory.getSocketFactory())
241
+                        .build(),
242
+                null,
243
+                null,
244
+                null
245
+        );
246
+        HttpClient httpClient = HttpClientBuilder.create()
247
+                .setConnectionManager(connManager)
248
+                .build();
249
+
250
+        HttpPost httpPost = new HttpPost(REPORT_URL);
251
+
252
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
253
+        httpPost.setConfig(requestConfig);
254
+
255
+        StringEntity postEntity = new StringEntity(data, "UTF-8");
256
+        httpPost.addHeader("Content-Type", "text/xml");
257
+        httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT);
258
+        httpPost.setEntity(postEntity);
259
+
260
+        HttpResponse httpResponse = httpClient.execute(httpPost);
261
+        HttpEntity httpEntity = httpResponse.getEntity();
262
+        return EntityUtils.toString(httpEntity, "UTF-8");
263
+    }
264
+
265
+}

+ 258
- 0
CODE/smart-community/app-api/src/main/java/com/community/huiju/common/wxpay/WXPayRequest.java 查看文件

@@ -0,0 +1,258 @@
1
+package com.community.huiju.common.wxpay;
2
+
3
+import org.apache.http.HttpEntity;
4
+import org.apache.http.HttpResponse;
5
+import org.apache.http.client.HttpClient;
6
+import org.apache.http.client.config.RequestConfig;
7
+import org.apache.http.client.methods.HttpPost;
8
+import org.apache.http.config.RegistryBuilder;
9
+import org.apache.http.conn.ConnectTimeoutException;
10
+import org.apache.http.conn.socket.ConnectionSocketFactory;
11
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
12
+import org.apache.http.conn.ssl.DefaultHostnameVerifier;
13
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
14
+import org.apache.http.entity.StringEntity;
15
+import org.apache.http.impl.client.HttpClientBuilder;
16
+import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
17
+import org.apache.http.util.EntityUtils;
18
+
19
+import javax.net.ssl.KeyManagerFactory;
20
+import javax.net.ssl.SSLContext;
21
+import java.io.InputStream;
22
+import java.net.SocketTimeoutException;
23
+import java.net.UnknownHostException;
24
+import java.security.KeyStore;
25
+import java.security.SecureRandom;
26
+
27
+import static com.community.huiju.common.wxpay.WXPayConstants.USER_AGENT;
28
+
29
+public class WXPayRequest {
30
+    private WXPayConfig config;
31
+    public WXPayRequest(WXPayConfig config) throws Exception{
32
+
33
+        this.config = config;
34
+    }
35
+
36
+    /**
37
+     * 请求,只请求一次,不做重试
38
+     * @param domain
39
+     * @param urlSuffix
40
+     * @param uuid
41
+     * @param data
42
+     * @param connectTimeoutMs
43
+     * @param readTimeoutMs
44
+     * @param useCert 是否使用证书,针对退款、撤销等操作
45
+     * @return
46
+     * @throws Exception
47
+     */
48
+    private String requestOnce(final String domain, String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert) throws Exception {
49
+        BasicHttpClientConnectionManager connManager;
50
+        if (useCert) {
51
+            // 证书
52
+            char[] password = config.getMchID().toCharArray();
53
+            InputStream certStream = config.getCertStream();
54
+            KeyStore ks = KeyStore.getInstance("PKCS12");
55
+            ks.load(certStream, password);
56
+
57
+            // 实例化密钥库 & 初始化密钥工厂
58
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
59
+            kmf.init(ks, password);
60
+
61
+            // 创建 SSLContext
62
+            SSLContext sslContext = SSLContext.getInstance("TLS");
63
+            sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
64
+
65
+            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
66
+                    sslContext,
67
+                    new String[]{"TLSv1"},
68
+                    null,
69
+                    new DefaultHostnameVerifier());
70
+
71
+            connManager = new BasicHttpClientConnectionManager(
72
+                    RegistryBuilder.<ConnectionSocketFactory>create()
73
+                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
74
+                            .register("https", sslConnectionSocketFactory)
75
+                            .build(),
76
+                    null,
77
+                    null,
78
+                    null
79
+            );
80
+        }
81
+        else {
82
+            connManager = new BasicHttpClientConnectionManager(
83
+                    RegistryBuilder.<ConnectionSocketFactory>create()
84
+                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
85
+                            .register("https", SSLConnectionSocketFactory.getSocketFactory())
86
+                            .build(),
87
+                    null,
88
+                    null,
89
+                    null
90
+            );
91
+        }
92
+
93
+        HttpClient httpClient = HttpClientBuilder.create()
94
+                .setConnectionManager(connManager)
95
+                .build();
96
+
97
+        String url = "https://" + domain + urlSuffix;
98
+        HttpPost httpPost = new HttpPost(url);
99
+
100
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
101
+        httpPost.setConfig(requestConfig);
102
+
103
+        StringEntity postEntity = new StringEntity(data, "UTF-8");
104
+        httpPost.addHeader("Content-Type", "text/xml");
105
+        httpPost.addHeader("User-Agent", USER_AGENT + " " + config.getMchID());
106
+        httpPost.setEntity(postEntity);
107
+
108
+        HttpResponse httpResponse = httpClient.execute(httpPost);
109
+        HttpEntity httpEntity = httpResponse.getEntity();
110
+        return EntityUtils.toString(httpEntity, "UTF-8");
111
+
112
+    }
113
+
114
+
115
+    private String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception {
116
+        Exception exception = null;
117
+        long elapsedTimeMillis = 0;
118
+        long startTimestampMs = WXPayUtil.getCurrentTimestampMs();
119
+        boolean firstHasDnsErr = false;
120
+        boolean firstHasConnectTimeout = false;
121
+        boolean firstHasReadTimeout = false;
122
+        IWXPayDomain.DomainInfo domainInfo = config.getWXPayDomain().getDomain(config);
123
+        if(domainInfo == null){
124
+            throw new Exception("WXPayConfig.getWXPayDomain().getDomain() is empty or null");
125
+        }
126
+        try {
127
+            String result = requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert);
128
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
129
+            config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, null);
130
+            WXPayReport.getInstance(config).report(
131
+                    uuid,
132
+                    elapsedTimeMillis,
133
+                    domainInfo.domain,
134
+                    domainInfo.primaryDomain,
135
+                    connectTimeoutMs,
136
+                    readTimeoutMs,
137
+                    firstHasDnsErr,
138
+                    firstHasConnectTimeout,
139
+                    firstHasReadTimeout);
140
+            return result;
141
+        }
142
+        catch (UnknownHostException ex) {  // dns 解析错误,或域名不存在
143
+            exception = ex;
144
+            firstHasDnsErr = true;
145
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
146
+            WXPayUtil.getLogger().warn("UnknownHostException for domainInfo {}", domainInfo);
147
+            WXPayReport.getInstance(config).report(
148
+                    uuid,
149
+                    elapsedTimeMillis,
150
+                    domainInfo.domain,
151
+                    domainInfo.primaryDomain,
152
+                    connectTimeoutMs,
153
+                    readTimeoutMs,
154
+                    firstHasDnsErr,
155
+                    firstHasConnectTimeout,
156
+                    firstHasReadTimeout
157
+            );
158
+        }
159
+        catch (ConnectTimeoutException ex) {
160
+            exception = ex;
161
+            firstHasConnectTimeout = true;
162
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
163
+            WXPayUtil.getLogger().warn("connect timeout happened for domainInfo {}", domainInfo);
164
+            WXPayReport.getInstance(config).report(
165
+                    uuid,
166
+                    elapsedTimeMillis,
167
+                    domainInfo.domain,
168
+                    domainInfo.primaryDomain,
169
+                    connectTimeoutMs,
170
+                    readTimeoutMs,
171
+                    firstHasDnsErr,
172
+                    firstHasConnectTimeout,
173
+                    firstHasReadTimeout
174
+            );
175
+        }
176
+        catch (SocketTimeoutException ex) {
177
+            exception = ex;
178
+            firstHasReadTimeout = true;
179
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
180
+            WXPayUtil.getLogger().warn("timeout happened for domainInfo {}", domainInfo);
181
+            WXPayReport.getInstance(config).report(
182
+                    uuid,
183
+                    elapsedTimeMillis,
184
+                    domainInfo.domain,
185
+                    domainInfo.primaryDomain,
186
+                    connectTimeoutMs,
187
+                    readTimeoutMs,
188
+                    firstHasDnsErr,
189
+                    firstHasConnectTimeout,
190
+                    firstHasReadTimeout);
191
+        }
192
+        catch (Exception ex) {
193
+            exception = ex;
194
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
195
+            WXPayReport.getInstance(config).report(
196
+                    uuid,
197
+                    elapsedTimeMillis,
198
+                    domainInfo.domain,
199
+                    domainInfo.primaryDomain,
200
+                    connectTimeoutMs,
201
+                    readTimeoutMs,
202
+                    firstHasDnsErr,
203
+                    firstHasConnectTimeout,
204
+                    firstHasReadTimeout);
205
+        }
206
+        config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, exception);
207
+        throw exception;
208
+    }
209
+
210
+
211
+    /**
212
+     * 可重试的,非双向认证的请求
213
+     * @param urlSuffix
214
+     * @param uuid
215
+     * @param data
216
+     * @return
217
+     */
218
+    public String requestWithoutCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {
219
+        return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), false, autoReport);
220
+    }
221
+
222
+    /**
223
+     * 可重试的,非双向认证的请求
224
+     * @param urlSuffix
225
+     * @param uuid
226
+     * @param data
227
+     * @param connectTimeoutMs
228
+     * @param readTimeoutMs
229
+     * @return
230
+     */
231
+    public String requestWithoutCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs,  boolean autoReport) throws Exception {
232
+        return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, false, autoReport);
233
+    }
234
+
235
+    /**
236
+     * 可重试的,双向认证的请求
237
+     * @param urlSuffix
238
+     * @param uuid
239
+     * @param data
240
+     * @return
241
+     */
242
+    public String requestWithCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {
243
+        return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), true, autoReport);
244
+    }
245
+
246
+    /**
247
+     * 可重试的,双向认证的请求
248
+     * @param urlSuffix
249
+     * @param uuid
250
+     * @param data
251
+     * @param connectTimeoutMs
252
+     * @param readTimeoutMs
253
+     * @return
254
+     */
255
+    public String requestWithCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {
256
+        return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, true, autoReport);
257
+    }
258
+}

+ 299
- 0
CODE/smart-community/app-api/src/main/java/com/community/huiju/common/wxpay/WXPayUtil.java 查看文件

@@ -0,0 +1,299 @@
1
+package com.community.huiju.common.wxpay;
2
+
3
+import com.community.huiju.common.wxpay.WXPayConstants.SignType;
4
+import org.slf4j.Logger;
5
+import org.slf4j.LoggerFactory;
6
+import org.w3c.dom.Node;
7
+import org.w3c.dom.NodeList;
8
+
9
+import javax.crypto.Mac;
10
+import javax.crypto.spec.SecretKeySpec;
11
+import javax.xml.parsers.DocumentBuilder;
12
+import javax.xml.transform.OutputKeys;
13
+import javax.xml.transform.Transformer;
14
+import javax.xml.transform.TransformerFactory;
15
+import javax.xml.transform.dom.DOMSource;
16
+import javax.xml.transform.stream.StreamResult;
17
+import java.io.ByteArrayInputStream;
18
+import java.io.InputStream;
19
+import java.io.StringWriter;
20
+import java.security.MessageDigest;
21
+import java.security.SecureRandom;
22
+import java.util.Arrays;
23
+import java.util.HashMap;
24
+import java.util.Map;
25
+import java.util.Random;
26
+import java.util.Set;
27
+
28
+
29
+public class WXPayUtil {
30
+
31
+    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
32
+
33
+    private static final Random RANDOM = new SecureRandom();
34
+
35
+    /**
36
+     * XML格式字符串转换为Map
37
+     *
38
+     * @param strXML XML字符串
39
+     * @return XML数据转换后的Map
40
+     * @throws Exception
41
+     */
42
+    public static Map<String, String> xmlToMap(String strXML) throws Exception {
43
+        try {
44
+            Map<String, String> data = new HashMap<String, String>();
45
+            DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
46
+            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
47
+            org.w3c.dom.Document doc = documentBuilder.parse(stream);
48
+            doc.getDocumentElement().normalize();
49
+            NodeList nodeList = doc.getDocumentElement().getChildNodes();
50
+            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
51
+                Node node = nodeList.item(idx);
52
+                if (node.getNodeType() == Node.ELEMENT_NODE) {
53
+                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
54
+                    data.put(element.getNodeName(), element.getTextContent());
55
+                }
56
+            }
57
+            try {
58
+                stream.close();
59
+            } catch (Exception ex) {
60
+                // do nothing
61
+            }
62
+            return data;
63
+        } catch (Exception ex) {
64
+            WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
65
+            throw ex;
66
+        }
67
+
68
+    }
69
+
70
+    /**
71
+     * 将Map转换为XML格式的字符串
72
+     *
73
+     * @param data Map类型数据
74
+     * @return XML格式的字符串
75
+     * @throws Exception
76
+     */
77
+    public static String mapToXml(Map<String, String> data) throws Exception {
78
+        org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
79
+        org.w3c.dom.Element root = document.createElement("xml");
80
+        document.appendChild(root);
81
+        for (String key: data.keySet()) {
82
+            String value = data.get(key);
83
+            if (value == null) {
84
+                value = "";
85
+            }
86
+            value = value.trim();
87
+            org.w3c.dom.Element filed = document.createElement(key);
88
+            filed.appendChild(document.createTextNode(value));
89
+            root.appendChild(filed);
90
+        }
91
+        TransformerFactory tf = TransformerFactory.newInstance();
92
+        Transformer transformer = tf.newTransformer();
93
+        DOMSource source = new DOMSource(document);
94
+        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
95
+        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
96
+        StringWriter writer = new StringWriter();
97
+        StreamResult result = new StreamResult(writer);
98
+        transformer.transform(source, result);
99
+        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
100
+        try {
101
+            writer.close();
102
+        }
103
+        catch (Exception ex) {
104
+        }
105
+        return output;
106
+    }
107
+
108
+
109
+    /**
110
+     * 生成带有 sign 的 XML 格式字符串
111
+     *
112
+     * @param data Map类型数据
113
+     * @param key API密钥
114
+     * @return 含有sign字段的XML
115
+     */
116
+    public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
117
+        return generateSignedXml(data, key, SignType.MD5);
118
+    }
119
+
120
+    /**
121
+     * 生成带有 sign 的 XML 格式字符串
122
+     *
123
+     * @param data Map类型数据
124
+     * @param key API密钥
125
+     * @param signType 签名类型
126
+     * @return 含有sign字段的XML
127
+     */
128
+    public static String generateSignedXml(final Map<String, String> data, String key, SignType signType) throws Exception {
129
+        String sign = generateSignature(data, key, signType);
130
+        data.put(WXPayConstants.FIELD_SIGN, sign);
131
+        return mapToXml(data);
132
+    }
133
+
134
+
135
+    /**
136
+     * 判断签名是否正确
137
+     *
138
+     * @param xmlStr XML格式数据
139
+     * @param key API密钥
140
+     * @return 签名是否正确
141
+     * @throws Exception
142
+     */
143
+    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
144
+        Map<String, String> data = xmlToMap(xmlStr);
145
+        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
146
+            return false;
147
+        }
148
+        String sign = data.get(WXPayConstants.FIELD_SIGN);
149
+        return generateSignature(data, key).equals(sign);
150
+    }
151
+
152
+    /**
153
+     * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
154
+     *
155
+     * @param data Map类型数据
156
+     * @param key API密钥
157
+     * @return 签名是否正确
158
+     * @throws Exception
159
+     */
160
+    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
161
+        return isSignatureValid(data, key, SignType.MD5);
162
+    }
163
+
164
+    /**
165
+     * 判断签名是否正确,必须包含sign字段,否则返回false。
166
+     *
167
+     * @param data Map类型数据
168
+     * @param key API密钥
169
+     * @param signType 签名方式
170
+     * @return 签名是否正确
171
+     * @throws Exception
172
+     */
173
+    public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
174
+        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
175
+            return false;
176
+        }
177
+        String sign = data.get(WXPayConstants.FIELD_SIGN);
178
+        return generateSignature(data, key, signType).equals(sign);
179
+    }
180
+
181
+    /**
182
+     * 生成签名
183
+     *
184
+     * @param data 待签名数据
185
+     * @param key API密钥
186
+     * @return 签名
187
+     */
188
+    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
189
+        return generateSignature(data, key, SignType.MD5);
190
+    }
191
+
192
+    /**
193
+     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
194
+     *
195
+     * @param data 待签名数据
196
+     * @param key API密钥
197
+     * @param signType 签名方式
198
+     * @return 签名
199
+     */
200
+    public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
201
+        Set<String> keySet = data.keySet();
202
+        String[] keyArray = keySet.toArray(new String[keySet.size()]);
203
+        Arrays.sort(keyArray);
204
+        StringBuilder sb = new StringBuilder();
205
+        for (String k : keyArray) {
206
+            if (k.equals(WXPayConstants.FIELD_SIGN)) {
207
+                continue;
208
+            }
209
+            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
210
+                sb.append(k).append("=").append(data.get(k).trim()).append("&");
211
+        }
212
+        sb.append("key=").append(key);
213
+        if (SignType.MD5.equals(signType)) {
214
+            return MD5(sb.toString()).toUpperCase();
215
+        }
216
+        else if (SignType.HMACSHA256.equals(signType)) {
217
+            return HMACSHA256(sb.toString(), key);
218
+        }
219
+        else {
220
+            throw new Exception(String.format("Invalid sign_type: %s", signType));
221
+        }
222
+    }
223
+
224
+
225
+    /**
226
+     * 获取随机字符串 Nonce Str
227
+     *
228
+     * @return String 随机字符串
229
+     */
230
+    public static String generateNonceStr() {
231
+        char[] nonceChars = new char[32];
232
+        for (int index = 0; index < nonceChars.length; ++index) {
233
+            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
234
+        }
235
+        return new String(nonceChars);
236
+    }
237
+
238
+
239
+    /**
240
+     * 生成 MD5
241
+     *
242
+     * @param data 待处理数据
243
+     * @return MD5结果
244
+     */
245
+    public static String MD5(String data) throws Exception {
246
+        MessageDigest md = MessageDigest.getInstance("MD5");
247
+        byte[] array = md.digest(data.getBytes("UTF-8"));
248
+        StringBuilder sb = new StringBuilder();
249
+        for (byte item : array) {
250
+            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
251
+        }
252
+        return sb.toString().toUpperCase();
253
+    }
254
+
255
+    /**
256
+     * 生成 HMACSHA256
257
+     * @param data 待处理数据
258
+     * @param key 密钥
259
+     * @return 加密结果
260
+     * @throws Exception
261
+     */
262
+    public static String HMACSHA256(String data, String key) throws Exception {
263
+        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
264
+        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
265
+        sha256_HMAC.init(secret_key);
266
+        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
267
+        StringBuilder sb = new StringBuilder();
268
+        for (byte item : array) {
269
+            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
270
+        }
271
+        return sb.toString().toUpperCase();
272
+    }
273
+
274
+    /**
275
+     * 日志
276
+     * @return
277
+     */
278
+    public static Logger getLogger() {
279
+        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
280
+        return logger;
281
+    }
282
+
283
+    /**
284
+     * 获取当前时间戳,单位秒
285
+     * @return
286
+     */
287
+    public static long getCurrentTimestamp() {
288
+        return System.currentTimeMillis()/1000;
289
+    }
290
+
291
+    /**
292
+     * 获取当前时间戳,单位毫秒
293
+     * @return
294
+     */
295
+    public static long getCurrentTimestampMs() {
296
+        return System.currentTimeMillis();
297
+    }
298
+
299
+}

+ 30
- 0
CODE/smart-community/app-api/src/main/java/com/community/huiju/common/wxpay/WXPayXmlUtil.java 查看文件

@@ -0,0 +1,30 @@
1
+package com.community.huiju.common.wxpay;
2
+
3
+import org.w3c.dom.Document;
4
+
5
+import javax.xml.XMLConstants;
6
+import javax.xml.parsers.DocumentBuilder;
7
+import javax.xml.parsers.DocumentBuilderFactory;
8
+import javax.xml.parsers.ParserConfigurationException;
9
+
10
+/**
11
+ * 2018/7/3
12
+ */
13
+public final class WXPayXmlUtil {
14
+    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
15
+        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
16
+        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
17
+        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
18
+        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
19
+        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
20
+        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
21
+        documentBuilderFactory.setXIncludeAware(false);
22
+        documentBuilderFactory.setExpandEntityReferences(false);
23
+
24
+        return documentBuilderFactory.newDocumentBuilder();
25
+    }
26
+
27
+    public static Document newDocument() throws ParserConfigurationException {
28
+        return newDocumentBuilder().newDocument();
29
+    }
30
+}

+ 54
- 0
CODE/smart-community/app-api/src/main/java/com/community/huiju/controller/WxPayController.java 查看文件

@@ -0,0 +1,54 @@
1
+package com.community.huiju.controller;
2
+
3
+import com.community.commom.mode.ResponseBean;
4
+import com.community.huiju.common.wxpay.HfWxConfig;
5
+import com.community.huiju.common.wxpay.WXPay;
6
+import com.google.common.collect.Maps;
7
+import io.swagger.annotations.Api;
8
+import io.swagger.annotations.ApiOperation;
9
+import org.springframework.cloud.context.config.annotation.RefreshScope;
10
+import org.springframework.web.bind.annotation.PathVariable;
11
+import org.springframework.web.bind.annotation.RequestMapping;
12
+import org.springframework.web.bind.annotation.RequestMethod;
13
+import org.springframework.web.bind.annotation.RestController;
14
+
15
+import java.util.HashMap;
16
+import java.util.Map;
17
+
18
+/**
19
+ * @author FXF
20
+ * @date 2019-02-19
21
+ */
22
+@RestController
23
+@RefreshScope
24
+@RequestMapping("/")
25
+@Api(value = "微信支付 API", description = "微信支付 API")
26
+public class WxPayController {
27
+	
28
+	@ApiOperation(value = "统一下单", notes = "统一下单")
29
+	@RequestMapping(value = "/wxUnifiedOrder",method = RequestMethod.GET)
30
+	public ResponseBean wxUnifiedOrder(){
31
+		ResponseBean responseBean = new ResponseBean();
32
+		HfWxConfig config = new HfWxConfig();
33
+		Map<String, String> resp = Maps.newHashMap();
34
+		try {
35
+			WXPay wxpay = new WXPay(config);
36
+			
37
+			Map<String, String> data = new HashMap<String, String>();
38
+			data.put("body", "支付测试");
39
+			data.put("out_trade_no", "2019021910595900000012");
40
+			data.put("device_info", "");
41
+			data.put("fee_type", "CNY");
42
+			data.put("total_fee", "1");
43
+			data.put("spbill_create_ip", "123.12.12.123");
44
+			data.put("notify_url", "http://www.example.com/wxpay/notify");
45
+			// 此处指定为APP支付
46
+			data.put("trade_type", "APP");
47
+			resp = wxpay.unifiedOrder(data);
48
+		} catch (Exception e) {
49
+			e.printStackTrace();
50
+		}
51
+		responseBean.addSuccess(resp);
52
+		return responseBean;
53
+	}
54
+}