package com.hx.corp.util; import com.alibaba.fastjson.JSON; import com.hx.corp.entity.CorpPayRequest; import com.hx.corp.entity.CorpPayResponse; import com.hx.exception.ServiceException; import com.hx.mp.util.*; import com.hx.util.SimpleTool; import com.hx.util.StringUtils; import net.sf.json.JSONObject; 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 WxCorpPayUtil { //log4j日志 private static Logger logger = LoggerFactory.getLogger(WxCorpPayUtil.class.getName()); // 退款接口连接 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"; /** 企业付款 * @param corpPayRequest 请求对象 * @param mchKey 商户号秘钥 * @param certPath 支付证书 * @return CorpPayResponse * @throws Exception */ public static CorpPayResponse qdCorpPay(CorpPayRequest corpPayRequest, String mchKey, String certPath) throws Exception { CorpPayResponse corpPayResponse = new CorpPayResponse(); SortedMap parameters = new TreeMap(); parameters.put("mch_appid", corpPayRequest.getMch_appid()); parameters.put("mchid", corpPayRequest.getMchid()); parameters.put("partner_trade_no", corpPayRequest.getPartner_trade_no()); //parameters.put("nonce_str", UUID.randomUUID().toString().substring(0, 30)); parameters.put("nonce_str", corpPayRequest.getNonce_str()); parameters.put("openid", corpPayRequest.getOpenid()); parameters.put("check_name", corpPayRequest.getCheck_name()); parameters.put("amount", corpPayRequest.getAmount().toString()); parameters.put("spbill_create_ip", corpPayRequest.getSpbill_create_ip()); parameters.put("desc", corpPayRequest.getDesc()); corpPayRequest.setSign(WXSignUtils.createSign("UTF-8", parameters, mchKey)); parameters.put("sign", corpPayRequest.getSign()); String xmlInfo = HttpXmlUtils.transferXml(parameters); try { CloseableHttpResponse response = HttpUtil.Post(CORP_PAY_URL, xmlInfo, true, certPath, corpPayRequest.getMchid()); String transfersXml = EntityUtils.toString(response.getEntity(), "utf-8"); //Map transferMap = HttpXmlUtils.parseRefundXml(transfersXml); Map transferMap = XMLUtil.doXMLParse(transfersXml); // 将 Map 转换为 实体类 corpPayResponse = JSON.parseObject(JSON.toJSONString(transferMap),CorpPayResponse.class); } catch (Exception e) { logger.error("企业付款接口报错",e); } return corpPayResponse; } /**统一支付 * @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; } /**统一支付(分账) * @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 交易类型 * @param profit_sharing 是否分账:N不,Y是 * @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,String profit_sharing) 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);// 附带数据包 reqHandler.setParameter("profit_sharing", profit_sharing);// 附带数据包 // ----------------------------- // 设置通信参数 // ----------------------------- // 设置请求返回的等待时间 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(""); SortedMap parameters = new TreeMap(); 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 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 params) { StringBuilder sb = new StringBuilder(); Set es = params.entrySet(); Iterator it = es.iterator(); sb.append("\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("\n"); } sb.append(""); return sb.toString(); } }