1、首先查看微信支付官方文档了解支付流程
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3
2、编写统一下单接口
public class WxPayUtil {
public static final String PAY_URL="https://api.mch.weixin.qq.com/pay/unifiedorder";
/**
* 统一下单
* @param body
* @param out_trade_no
* @param total_fee
* @param IP
* @param notify_url
* @param openid
* @return
* @throws IOException
*/
public static Map<String,Object> unifiedOrder(String body,String out_trade_no,Double third_fee,String openid,HttpServletRequest request)throws IOException {
//设置访问路径
HttpPost httppost = new HttpPost(PAY_URL);
String IP = getIpAddr(request),prePath = SystemPath.getRequestProjectUrl(request);
String nonce_str = getNonceStr().toUpperCase();//随机
String notify_url = String.format(WeixinUtil.NOTIFY_URL, prePath);
Long total_fee = Double.valueOf(DoubleUtil.mul(third_fee, 100)+"").longValue();
//组装请求参数,按照ASCII排序
String sign = "appid=" + WeixinUtil.APPID +
"&body=" + body +
"&mch_id=" + WeixinUtil.MCH_ID +
"&nonce_str=" + nonce_str +
"¬ify_url=" +notify_url+
"&openid=" + openid +
"&out_trade_no=" + out_trade_no +
"&spbill_create_ip=" + IP +
"&total_fee=" + total_fee.toString() +
"&trade_type=" + WeixinUtil.TRADE_TYPE_JS +
"&key=" + WeixinUtil.KEY;//这个字段是用于之后MD5加密的,字段要按照ascii码顺序排序
sign = MD5Util.MD5Encode(sign.toString(), "UTF-8").toUpperCase();
//组装包含openid用于请求统一下单返回结果的XML
StringBuilder sb = new StringBuilder("");
sb.append("<xml>");
setXmlKV(sb,"appid",WeixinUtil.APPID);
setXmlKV(sb,"body",body);
setXmlKV(sb,"mch_id",WeixinUtil.MCH_ID);
setXmlKV(sb,"nonce_str",nonce_str);
setXmlKV(sb,"notify_url",notify_url);
setXmlKV(sb,"openid",openid);
setXmlKV(sb,"out_trade_no",out_trade_no);
setXmlKV(sb,"spbill_create_ip",IP);
setXmlKV(sb,"total_fee",total_fee.toString());
setXmlKV(sb,"trade_type",WeixinUtil.TRADE_TYPE_JS);
setXmlKV(sb,"sign",sign);
sb.append("</xml>");
System.out.println("统一下单请求:" + sb);
StringEntity reqEntity = new StringEntity(new String (sb.toString().getBytes("UTF-8"),"ISO8859-1"));//这个处理是为了防止传中文的时候出现签名错误
httppost.setEntity(reqEntity);
CloseableHttpClient httpclient = HttpClients.createDefault();
CloseableHttpResponse response = httpclient.execute(httppost);
String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));
System.out.println("统一下单返回xml:" + strResult);
return WxPayUtil.getPayMap(strResult);
}
/**
* 根据统一下单返回预支付订单的id和其他信息生成签名并拼装为map(调用微信支付)
* @param prePayInfoXml
* @return
*/
public static Map<String,Object> getPayMap(String prePayInfoXml){
Map<String,Object> map = new HashMap<String,Object>();
String prepay_id = getXmlPara(prePayInfoXml,"prepay_id");//统一下单返回xml中prepay_id
String timeStamp = String.valueOf((System.currentTimeMillis()/1000));//1970年到现在的秒数
String nonceStr = getNonceStr().toUpperCase();//随机数据字符串
String packageStr = "prepay_id=" + prepay_id;
String signType = WeixinUtil.SIGN_TYPE;
String paySign =
"appId=" + WeixinUtil.APPID +
"&nonceStr=" + nonceStr +
"&package=prepay_id=" + prepay_id +
"&signType=" + signType +
"&timeStamp=" + timeStamp +
"&key="+ WeixinUtil.KEY;//注意这里的参数要根据ASCII码 排序
paySign = MD5Util.MD5Encode(paySign, "UTF-8").toLowerCase();//将数据MD5加密
map.put("appId",WeixinUtil.APPID);
map.put("timeStamp",timeStamp);
map.put("nonceStr",nonceStr);
map.put("packageStr",packageStr);
map.put("signType",signType);
map.put("paySign",paySign);
map.put("prepay_id",prepay_id);
return map;
}
/**
* 获取32位随机字符串
* @return
*/
public static String getNonceStr(){
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder sb = new StringBuilder();
Random rd = new Random();
for(int i = 0 ; i < 32 ; i ++ ){
sb.append(str.charAt(rd.nextInt(str.length())));
}
return sb.toString();
}
/**
* 插入XML标签
* @param sb
* @param Key
* @param value
* @return
*/
public static StringBuilder setXmlKV(StringBuilder sb,String Key,String value){
sb.append("<");
sb.append(Key);
sb.append(">");
sb.append(value);
sb.append("</");
sb.append(Key);
sb.append(">");
return sb;
}
/**
* 解析XML 获得名称为para的参数值
* @param xml
* @param para
* @return
*/
public static String getXmlPara(String xml,String para){
int start = xml.indexOf("<"+para+">");
int end = xml.indexOf("</"+para+">");
if(start < 0 && end < 0){
return null;
}
return xml.substring(start + ("<"+para+">").length(),end).replace("<![CDATA[","").replace("]]>","");
}
/**
* 获取ip地址
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
InetAddress addr = null;
try {
addr = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
System.out.println("ip地址2:"+request.getRemoteAddr());
return request.getRemoteAddr();
}
byte[] ipAddr = addr.getAddress();
String ipAddrStr = "";
for (int i = 0; i < ipAddr.length; i++) {
if (i > 0) {
ipAddrStr += ".";
}
ipAddrStr += ipAddr[i] & 0xFF;
}
System.out.println("ip地址:"+ipAddrStr);
return ipAddrStr;
}
/**
* 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
* @return boolean
*/
public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams) {
StringBuffer 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(!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + WeixinUtil.KEY);
//算出摘要
String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase();
String tenpaySign = ((String)packageParams.get("sign")).toLowerCase();
// System.out.println(tenpaySign + " " + mysign);
return tenpaySign.equals(mysign);
}
}
3、微信支付回调接口
/**
* 微信异步通知---js
*/
@Transactional
@RequestMapping(value="/wxJSNotify.do", produces = "text/html;charset=UTF-8",method={RequestMethod.POST})
public String wxJSNotify(HttpServletRequest request,HttpServletResponse response) throws Exception{
System.out.println("===============wxJSNotify================");
logger.warn("===============wxJSNotify================");
//读取参数
InputStream inputStream = request.getInputStream();
StringBuffer sb = new StringBuffer();
String s ;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null){
sb.append(s);
}
in.close();
inputStream.close();
//解析xml成map
Map<String, String> m = new HashMap<String, String>();
m = XMLUtil.doXMLParse(sb.toString());
/*for(Object keyValue : m.keySet()){
System.out.println(keyValue+"="+m.get(keyValue));
}*/
//过滤空 设置 TreeMap
SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();
Iterator<String> it = m.keySet().iterator();
while (it.hasNext()) {
String parameter = it.next();
String parameterValue = m.get(parameter);
String v = "";
if(null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
System.out.println("===============微信js回调回来的参数================"+JSONObject.fromObject(packageParams).toString());
logger.warn("===============微信js回调回来的参数================"+JSONObject.fromObject(packageParams).toString());
//判断签名是否正确
if(WxPayUtil.isTenpaySign("UTF-8", packageParams)) {
String out_trade_no = (String)packageParams.get("out_trade_no"); //商户订单号
//String attach = (String)packageParams.get("attach"); //附加参数
String trade_status = (String)packageParams.get("result_code");
System.out.println("========微信js中trade_status========"+trade_status);
logger.warn("========微信js中trade_status========"+trade_status);
if("SUCCESS".equals(trade_status)){
// 这里是支付成功
//执行自己的业务逻辑
String mch_id = (String)packageParams.get("mch_id"); //商户号
// String openid = (String)packageParams.get("openid"); //用户标识
String total_fee = (String)packageParams.get("total_fee");
String transaction_id = (String)packageParams.get("transaction_id"); //微信支付订单号
if(!WeixinUtil.MCH_ID.equals(mch_id)){
System.out.println("微信js中支付失败,错误信息:" + "参数错误");
logger.warn("微信js中支付失败,错误信息:" + "参数错误");
return "FAIL";
}else{
System.out.println("微信js支付成功");
logger.warn("微信js支付成功");
return successOrder(out_trade_no);
}
}else {
System.out.println("微信js支付失败");
logger.warn("微信js支付失败");
return failOrder(out_trade_no);
}
} else{
System.out.println("微信js中通知签名验证失败");
logger.warn("微信js中通知签名验证失败");
}
return "FAIL";
}
注意:微信支付回调接口需要返回
"<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[参数错误]]></return_msg>" + "</xml> ";或"<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";这种格式,不然会一直触发回调接口