package com.hx.mp.util;
|
|
import com.hx.exception.ServiceException;
|
import com.hx.util.SimpleTool;
|
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 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, total_fee, out_refund_no, refund_fee);
|
try {
|
StringEntity se = new StringEntity(xml);
|
httppost.setEntity(se);
|
System.out.println("executing request" + httppost.getRequestLine());
|
CloseableHttpResponse responseEntry = httpclient.execute(httppost);
|
try {
|
HttpEntity entity = responseEntry.getEntity();
|
System.out.println(responseEntry.getStatusLine());
|
if (entity != null) {
|
SAXReader saxReader = new SAXReader();
|
Document document = saxReader.read(entity.getContent());
|
Element rootElt = document.getRootElement();
|
String resultCode = rootElt.elementText("result_code");
|
JSONObject result = new JSONObject();
|
|
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"));
|
}
|
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);
|
parameters.put("out_trade_no", out_trade_no);
|
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();
|
}
|
}
|