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