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