E1ED922C1E9526DD63272D7EC5C6CB77
2020-12-29 387efa6915ab2aa976eebff4d53d7875caa54f49
提交 | 用户 | age
5c5945 1 package com.hx.mp.util;
E 2
3 import com.hx.exception.ServiceException;
4 import com.hx.util.SimpleTool;
58a62d 5 import com.hx.util.StringUtils;
5c5945 6 import net.sf.json.JSONObject;
58a62d 7 import org.apache.commons.io.IOUtils;
5c5945 8 import org.apache.http.HttpEntity;
E 9 import org.apache.http.client.methods.CloseableHttpResponse;
10 import org.apache.http.client.methods.HttpPost;
11 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
12 import org.apache.http.entity.StringEntity;
13 import org.apache.http.impl.client.CloseableHttpClient;
14 import org.apache.http.impl.client.HttpClients;
15 import org.apache.http.ssl.SSLContexts;
16 import org.apache.http.util.EntityUtils;
17 import org.dom4j.Document;
18 import org.dom4j.Element;
19 import org.dom4j.io.SAXReader;
20
21 import javax.net.ssl.SSLContext;
22 import javax.servlet.http.HttpServletRequest;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.security.KeyStore;
26 import java.util.*;
27
28
29 /** 微信支付/退款
30  * @author ChenJiaHe
31  */
32 public class WXPayUtil {
33
34     // 退款接口连接
35     private static final String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
36      /**查询订单链接*/
37     @SuppressWarnings("unused")
38     private static final String QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
39
40     /**同意下单链接*/
41     private static final String FIRST_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
42
43     // 企业付款
44     private static final String CORP_PAY_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
45
46
47     /** 企业付款*/
48     public static JSONObject qdCorpPay(String appId, String orderNo, String certPath, String mchid, String mchKey, String openId, String payFee, String desc)
49             throws Exception {
50
51         SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
52         parameters.put("mch_appid", appId);
53         parameters.put("mchid", mchid);
54         parameters.put("partner_trade_no", orderNo);
55         parameters.put("nonce_str", UUID.randomUUID().toString().substring(0, 30));
56         parameters.put("openid", openId);
57         parameters.put("check_name", "NO_CHECK");
58         parameters.put("amount", payFee);
59         parameters.put("spbill_create_ip", "8.8.8.8");
60         parameters.put("desc", desc);
61
62         String sign = WXSignUtils.createSign("UTF-8", parameters, mchKey);
63
64         parameters.put("sign", sign);
65         String xmlInfo = HttpXmlUtils.transferXml(parameters);
66
67         JSONObject returnObj = new JSONObject();
68
69         try {
70             CloseableHttpResponse response = HttpUtil.Post(CORP_PAY_URL, xmlInfo, true, certPath, mchid);
71             String transfersXml = EntityUtils.toString(response.getEntity(), "utf-8");
72             // System.out.println("渠道端企业付款:" + transfersXml);
73             Map<String, String> transferMap = HttpXmlUtils.parseRefundXml(transfersXml);
74             boolean bl = false;
75             if (transferMap.size() > 0) {
76                 if (transferMap.get("return_code").equals("SUCCESS")) {
77                     // 通讯成功
78                     if (transferMap.get("result_code").equals("SUCCESS")) {
79                         // 成功需要进行的逻辑操作
80                         returnObj.put("status", "suc");
81                     } else {
82                         bl = true;
83                         returnObj.put("status", "fail");
84                         returnObj.put("errMsg", transferMap.get("err_code") + "|" + transferMap.get("err_code_des"));
85                     }
86                 } else {
87                     bl = true;
88                     // 通讯不成功
89                     returnObj.put("status", "fail");
90                     returnObj.put("errMsg", transferMap.get("return_msg"));
91                 }
92             } else {
93                 bl = true;
94                 returnObj.put("status", "fail");
95                 returnObj.put("errMsg", "返回为空");
96             }
97             if (bl) {
98                 System.out.println("企业付款失败:" + transfersXml);
99             }
100         } catch (Exception e) {
101             e.printStackTrace();
102             returnObj.put("status", "fail");
103             returnObj.put("errMsg", e.getMessage());
104         }
105
106         return returnObj;
107     }
108
109     /**统一支付
110      * @param request 方法获取
111      * @param appId  小程序号
112      * @param partner  商户号
113      * @param key  秘钥
114      * @param notifyUrl  回调链接
115      * @param out_trade_no  订单号
116      * @param body 商品描述
117      * @param total_fee 支付金额
118      * @param openid 用户openId
119      * @param attach 附带数据包
120      * @param notifyUrl 回调通知地址
121      * @param trade_type 交易类型
122      * @return JSON  status = "SUC"为成功
123      */
124     public static JSONObject unifiedPay(HttpServletRequest request,String appId,String partner,String key,String notifyUrl,String out_trade_no, String body, String total_fee, String openid,
125             String attach,String trade_type) throws Exception {
126
127         if (!SimpleTool.checkNotNull(notifyUrl)) {
128             throw new ServiceException("支付功能故障!");
129         }
130
131         // 创建查询请求对象
132         RequestHandler reqHandler = new RequestHandler(null, null);
133         // 通信对象
134         TenpayHttpClient httpClient = new TenpayHttpClient();
135         // 应答对象
136         ClientResponseHandler resHandler = new ClientResponseHandler();
137
138         // -----------------------------
139         // 设置请求参数
140         // -----------------------------
141         // reqHandler.init();
142         reqHandler.setKey(key);
143         reqHandler.setGateUrl(FIRST_ORDER_URL);// 请求URL
144
145         // -----------------------------
146         // 设置接口参数(sign后台自动生成)
147         // -----------------------------
148         reqHandler.setParameter("appid", appId); // 公众号/小程序
149         reqHandler.setParameter("mch_id", partner); // 商户号
150         reqHandler.setParameter("nonce_str", SimpleTool.getUUIDName().substring(0, 30));// 随机乱码
151         reqHandler.setParameter("body", body);// 商品描述
152         reqHandler.setParameter("out_trade_no", out_trade_no);// 商户订单号
153         reqHandler.setParameter("total_fee", total_fee);// 总金额
154         reqHandler.setParameter("spbill_create_ip", "8.8.8.8");// 终端IP
155         reqHandler.setParameter("notify_url",notifyUrl);// 通知地址
156         reqHandler.setParameter("trade_type", trade_type);// 交易类型
157                                                           // JSAPI,NATIVE,APP
158         reqHandler.setParameter("openid", openid);// openId
159         reqHandler.setParameter("attach", attach);// 附带数据包
160
161         // -----------------------------
162         // 设置通信参数
163         // -----------------------------
164         // 设置请求返回的等待时间
165         httpClient.setTimeOut(5);
166
167         // 设置ca证书
168         // httpClient.setCaInfo(new File(CA_PATH));
169
170         // 设置个人(商户)证书
171         // httpClient.setCertInfo(new File(CERT_PATH), CERT_PWD);
172
173         // 设置发送类型POST
174         httpClient.setMethod("POST");
175
176         // 设置请求内容(生成sign)
177         String requestUrl = reqHandler.getRequestURL();// 组拼https://www.baidu.com?a=x&b=xx
178
179         httpClient.setReqContent(requestUrl);// https://www.baidu.com?a=x&b=xx
180         String rescontent = "null";
181
182         httpClient.setRequestHandler(reqHandler);// 把处理对象,像是参数各种东西都设置进去方便获取(quan)
183
184         // 返回出去的对象(状态,错误原因,该操作相关信息(参数,返回值))
185         JSONObject returnObj = new JSONObject();
186
187         // 后台调用
188         if (httpClient.call()) {
189             System.out.println("统一下单,成功cll了::");
190
191             // 设置结果参数
192             rescontent = httpClient.getResContent();
193             System.out.println("统一下单返回结果:" + rescontent);
194             resHandler.setContent(rescontent);// 解析xml
195             resHandler.setKey(key);
196
197             // 获取返回参数
198             String return_code = resHandler.getParameter("return_code");
199             String return_msg = resHandler.getParameter("return_msg");
200
201             // 判断签名及结果
202             if (resHandler.isTenpaySign() && "SUCCESS".equals(return_code)) {
203                 String prepay_id = resHandler.getParameter("prepay_id");// 预支付交易会话标识
204                 String code_url = resHandler.getParameter("code_url");// 二维码链接
205
206                 String result_code = resHandler.getParameter("result_code");// 业务结果
207                 String appid = resHandler.getParameter("appid");// 公众账号ID
208                 String mch_id = resHandler.getParameter("mch_id");// 商户号
209                 String nonce_str = resHandler.getParameter("nonce_str");// 随机码
210                 String sign = resHandler.getParameter("sign");// 签名
211
212                 if (result_code.equals("SUCCESS")) {
213                     returnObj.put("status", "suc");
214                     returnObj.put("sign", sign);
215                     returnObj.put("nonce_str", nonce_str);
216                     returnObj.put("mch_id", mch_id);
217                     returnObj.put("appid", appid);
218                     returnObj.put("prepay_id", prepay_id);
219                     returnObj.put("code_url", code_url);
220                     returnObj.put("out_trade_no", out_trade_no);
221                 } else {
222                     String errMsg = "[ERROR]result_code:" + resHandler.getParameter("result_code") + " err_code:"
223                             + resHandler.getParameter("err_code") + "err_code_des:"
224                             + resHandler.getParameter("err_code_des");
225
226                     // 错误时,返回结果未签名,记录retcode、retmsg看失败详情。
227                     returnObj.put("status", "ERROR-C");
228                     returnObj.put("errMsg", errMsg);
229                 }
230             } else {
231                 String errMsg = "return_code:" + return_code + "err_code:" + resHandler.getParameter("err_code")
232                         + " return_msg:" + return_msg;
233                 // 错误时,返回结果未签名,记录retcode、retmsg看失败详情。
234                 returnObj.put("status", "ERROR-B");
235                 returnObj.put("errMsg", errMsg);
236             }
237         } else {
238             // 有可能因为网络原因,请求已经处理,但未收到应答。
239             returnObj.put("status", "ERROR-A");
240             returnObj.put("errMsg", httpClient.getResponseCode() + ":" + httpClient.getErrInfo());
241         }
242
243         // 获取debug信息,建议把请求、应答内容、debug信息,通信返回码写入日志,方便定位问题
244         String detail = "http res:" + httpClient.getResponseCode() + "," + httpClient.getErrInfo() + ";" + "req url:"
245                 + requestUrl + ";" + ";" + "req debug:" + reqHandler.getDebugInfo() + ";" + "res content:" + rescontent
246                 + ";" + "res debug:" + resHandler.getDebugInfo() + ";";
247
248         returnObj.put("detail", detail);
249
250         
251         return returnObj;
252     }
253
254     /**处理信息
255      */
256     public static JSONObject paymentData(JSONObject payObj,String key){
257         JSONObject wxObj = new JSONObject();
258         /**统一下单*/
259         String payStatus = payObj.getString("status");
260         if (payStatus.equals("suc")) {
261             // JSONObject payObj = po.getJSONObject("inf");
262             String appId = payObj.getString("appid");
263             String nonceStr = payObj.getString("nonce_str");
264             String prepay_id = payObj.getString("prepay_id");
265             // JSAPI调用支付返回的数据
266             String timeStamp = SimpleTool.getTenTime(new Date()).toString();
267             String signType = "MD5";
268             String packagef = "prepay_id=" + prepay_id;
269             RequestHandler reqHandler = new RequestHandler(null, null);
270             reqHandler.setParameter("appId", appId);
271             reqHandler.setParameter("nonceStr", nonceStr);
272             reqHandler.setParameter("timeStamp", timeStamp);
273             reqHandler.setParameter("package", packagef);
274             reqHandler.setParameter("signType", signType);
275             reqHandler.setKey(key);
276             String paySign = reqHandler.createSign();// 生成签名
277             wxObj.put("orderNo", payObj.getString("out_trade_no"));
278             wxObj.put("paySign", paySign);
279             wxObj.put("appId", appId);
280             wxObj.put("nonceStr", nonceStr);
281             wxObj.put("package", packagef);
282             wxObj.put("timeStamp", timeStamp);
283         } else {
284             throw new RuntimeException(payObj.toString());
285         }
286         return wxObj;
287     }
288
289     /**
290      * 退款
291      * @param appId 小程序/公众号 appId
292      * @param partner 商户号
293      * @param key 商户号秘钥
294      * @param certPath 个人商户证书
295      * @param out_trade_no 商户订单号
296      * @param transaction_id 财付通订单号(微信订单号)
297      * @param out_refund_no 商户退单号
298      * @param total_fee 订单总额(单位:分)
299      * @param refund_fee 退款金额(单位:分)
300      * @return JSON status="SUCCESS"(成功) (状态,错误原因,该操作相关信息(参数,返回值))
301      */
302     public static JSONObject refund(String appId,String partner,String key,String certPath,String out_trade_no, String transaction_id, String out_refund_no, String total_fee,
303             String refund_fee) {
304          try{  
305              KeyStore keyStore = KeyStore.getInstance("PKCS12");
306              FileInputStream instream = new FileInputStream(new File(certPath));
307              try {  
308                  keyStore.load(instream,partner.toCharArray());
309              }finally {  
310                  instream.close();  
311              }  
312              // Trust own CA and all self-signed certs  
313              SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore,partner.toCharArray()).build();
314              // Allow TLSv1 protocol only  
315              SSLConnectionSocketFactory sslsf;
316             sslsf = new SSLConnectionSocketFactory(
317                     sslcontext, new String[] { "TLSv1" }, null,
318                     SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
319             CloseableHttpClient httpclient = HttpClients.custom()
320                      .setSSLSocketFactory(sslsf).build();  
321              HttpPost httppost = new HttpPost(REFUND_URL);
1887bd 322              String xml = wxPayRefundData(appId, partner, key, out_trade_no, transaction_id, out_refund_no, total_fee, refund_fee);
E 323              try {
5c5945 324                  StringEntity se = new StringEntity(xml);
E 325                  httppost.setEntity(se);  
58a62d 326
5c5945 327                  CloseableHttpResponse responseEntry = httpclient.execute(httppost);
E 328                  try {  
329                      HttpEntity entity = responseEntry.getEntity();
330                      if (entity != null) {
58a62d 331
5c5945 332                          SAXReader saxReader = new SAXReader();
E 333                          Document document = saxReader.read(entity.getContent());
334                          Element rootElt = document.getRootElement();
58a62d 335                          String returnCode = rootElt.elementText("return_code");
E 336                          JSONObject result = new JSONObject();
5c5945 337
58a62d 338                          if(returnCode.equals("SUCCESS")){
E 339                              String resultCode = rootElt.elementText("result_code");
340                              if(resultCode.equals("SUCCESS")) {
341                                  result.put("weixinPayUrl", rootElt.elementText("code_url"));
342                                  result.put("prepayId", rootElt.elementText("prepay_id"));
343                                  result.put("msg", "success");
344
345                                  String refund_id = rootElt.elementText("refund_id");//微信退款单号
346                                  String r_out_refund_no = rootElt.elementText("out_refund_no");
347                                  String errMsg = "商户号" + r_out_refund_no + "的退款流水号是:" + refund_id;
348                                  result.put("status", "SUCCESS");
349                                  result.put("errMsg", errMsg);
350                                  result.put("refund_id", refund_id);
351                              }else{
352                                  String errMsg = "[ERROR]result_code:" + rootElt.elementText("result_code")+
353                                          " err_code:" + rootElt.elementText("err_code");
354
355                                  //错误时,返回结果未签名,记录retcode、retmsg看失败详情。
356                                  result.put("errMsg", errMsg);
357                                  result.put("status","false");
358                                  result.put("msg",rootElt.elementText("err_code_des"));
359                              }
5c5945 360                          }else{  
58a62d 361                              String errMsg = "[ERROR]return_code:" + rootElt.elementText("return_code");
5c5945 362                              
E 363                              //错误时,返回结果未签名,记录retcode、retmsg看失败详情。
364                              result.put("errMsg", errMsg);
365                              result.put("status","false");
58a62d 366                              result.put("msg",rootElt.elementText("return_msg"));
5c5945 367                          }  
E 368                          return result;  
369                      }  
370                      EntityUtils.consume(entity);
371                  }  
372                  finally {  
373                      responseEntry.close();  
374                  }  
375              }  
376              finally {  
377                  httpclient.close();  
378              }  
379              return null;  
380          }catch(Exception e){  
381              e.printStackTrace();  
382              JSONObject result = new JSONObject();  
383              result.put("status","error");  
384              result.put("msg",e.getMessage());  
385              return result;  
386          }  
387     }
388
389     /** 封装参数数据
390      * @param appId 小程序/公众号 appId
391      * @param partner 商户号
392      * @param key 商户号秘钥
393      * @param out_trade_no 商户订单号
394      * @param transaction_id 财付通订单号(微信订单号)
395      * @param out_refund_no 商户退单号
396      * @param total_fee 订单总额(单位:分)
397      * @param refund_fee 退款金额(单位:分)
398      * @return
399      */
400     public static String wxPayRefundData(String appId,String partner,String key,String out_trade_no, String transaction_id,String out_refund_no,String total_fee,String refund_fee) {
401         StringBuffer xml = new StringBuffer();
402         String data = null;
403         try {
404             String nonceStr = SimpleTool.getUUIDName().substring(0,30);
405             xml.append("</xml>");
406             SortedMap<String,String> parameters = new TreeMap<String,String>();
407             parameters.put("appid",appId);
408             parameters.put("mch_id",partner);
409             parameters.put("nonce_str", nonceStr);
58a62d 410             if(!StringUtils.isEmpty(out_trade_no)) {
E 411                 parameters.put("out_trade_no", out_trade_no);
412             }
413             if(!StringUtils.isEmpty(transaction_id)) {
414                 parameters.put("transaction_id", transaction_id);
415             }
5c5945 416             parameters.put("out_refund_no", out_refund_no);
E 417             parameters.put("fee_type", "CNY");
418             parameters.put("total_fee", total_fee);//总金额
419             parameters.put("refund_fee", refund_fee);//退款金额
420             parameters.put("op_user_id",partner);
421             parameters.put("sign", createSign(parameters,key));
422             
423             data =SortedMaptoXml(parameters);
424         } catch (Exception e) {
425             System.err.println(e.getMessage());
426             return null;
427         }
428         return data;
429     }
430     
431     /**
432      * 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
433      */
434     public static String createSign(SortedMap<String, String> packageParams, String AppKey) {
435         StringBuffer sb;
436         sb = new StringBuffer();
437         Set es = packageParams.entrySet();
438         Iterator it = es.iterator();
439         while (it.hasNext()) {
440             Map.Entry entry = (Map.Entry) it.next();
441             String k = (String) entry.getKey();
442             String v = (String) entry.getValue();
443             if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
444                 sb.append(k + "=" + v + "&");
445             }
446         }
447         sb.append("key=" + AppKey);
448         String sign = MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase();
449         return sign;
450     }
451     
452     /**
453      * @Author: HONGLINCHEN
454      * @Description:请求值转换为xml格式 SortedMap转xml
455      * @param params
456      * @Date: 2017-9-7 17:18
457      */
458     private static String SortedMaptoXml(SortedMap<String,String> params) {
459         StringBuilder sb = new StringBuilder();
460         Set es = params.entrySet();
461         Iterator it = es.iterator();
462         sb.append("<xml>\n");
463         while(it.hasNext()) {
464             Map.Entry entry = (Map.Entry)it.next();
465             String k = (String)entry.getKey();
466             Object v = entry.getValue();
467             sb.append("<"+k+">");
468             sb.append(v);
469             sb.append("</"+k+">\n");
470         }
471         sb.append("</xml>");
472         return sb.toString();
473     }
474 }