第一个坑,签名获取,微信是两次签名加密,要按照微信官方文档一步步查看参数是否正确.
第二个坑,md5加密,我用到网上的md5加密方法,当小程序调起微信支付的时候一直报签名失败,最后发现是签名的md5加密的问题.
第三个坑,两次签名加密如果用到时间戳和随机字符串,一定要保持一致,用同一个,否则也会报出签名异常.
第四个坑,微信支付不能重复提交不一样的订单,比如:用户待付款,商家修改价格,然后提交就会出现订单重复问题.解决方案:就是业务订单号和支付订单号分开处理,每次支付都生成支付单号就也可以解决.
下面是微信支付的工具类,只需要替换一下
小程序ID
商户号
密钥key
package com.gqsh.other.pay.wechat;
/**
* 小程序微信支付工具类
*
* @author wsh
*/
import com.gqsh.other.entity.WxPayParams;
import com.gqsh.other.entity.WxQueryResult;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springblade.core.tool.utils.StringUtil;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Function: 小程序微信支付工具类
*
* @author att
* @version 1.0
* @since JDK1.8
*/
@Slf4j
public class WxPayUtil {
/**
* 小程序ID
*/
private final static String APPID = " ";
/**
* 商户号
*/
private final static String MCH_ID = " ";
/**
* 密钥
*/
private final static String KEY = " ";
/**
* 餐见小程序(随便写的,没有实际意义,微信必传参数)
*/
private final static String BODY = "cjxcx";
/**
* 交易类型
*/
private final static String TRADE_TYPE_JSAPI = "JSAPI";
/**
* 微信统一下单路径
*/
private final static String WX_TYXD_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/**
* 微信查询支付结果路径
*/
private final static String WX_ORDERQUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
/**
* 签名类型
*/
private final static String SIGN_TYPE_MD5 = "MD5";
public final static String SUCCESS = "SUCCESS";
private final static String FAIL = "FAIL";
private final static String OK = "OK";
/**
* 获取当前机器的ip
*
* @return String
*/
public static String getLocalIp() {
InetAddress ia = null;
String localip = null;
try {
ia = ia.getLocalHost();
localip = ia.getHostAddress();
} catch (Exception e) {
e.printStackTrace();
}
return localip;
}
/**
* Map转换为 Xml
*
* @param map
* @return Xml
* @throws Exception
*/
public static String mapToXml(SortedMap<String, String> map) throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//防止XXE攻击
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
org.w3c.dom.Document document = documentBuilder.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key : map.keySet()) {
String value = map.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString();
try {
writer.close();
} catch (Exception ex) {
}
return output;
}
/**
* 创建签名Sign
*
* @param key
* @param parameters
* @return
*/
public static String createSign(SortedMap<String, String> parameters, String key) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator<?> it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
if (entry.getValue() != null || !"".equals(entry.getValue())) {
String v = String.valueOf(entry.getValue());
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
}
sb.append("key=" + key);
String sign = DigestUtils.md5Hex(getContentBytes(sb.toString(), "UTF-8")).toUpperCase();
return sign;
}
/**
* XML转换为Map
*
* @param strXML
* @return Map
* @throws Exception
*/
public static Map<String, Object> getMapFromXML(String strXML) throws Exception {
try {
Map<String, Object> data = new HashMap<String, Object>();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//防止XXE攻击
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
ex.printStackTrace();
}
return data;
} catch (Exception ex) {
throw ex;
}
}
/**
* 生成随机数
*
* @return
*/
public static String makeUUID(int len) {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, len);
}
/**
* 获取当前的Timestamp
*
* @return
*/
public static String getCurrentTimeStamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
/**
* Xml字符串转换为Map
*
* @param xmlStr
* @return
*/
public static Map<String, String> xmlStrToMap(String xmlStr) {
Map<String, String> map = new HashMap<String, String>();
Document doc;
try {
doc = DocumentHelper.parseText(xmlStr);
Element root = doc.getRootElement();
List children = root.elements();
if (children != null && children.size() > 0) {
for (int i = 0; i < children.size(); i++) {
Element child = (Element) children.get(i);
map.put(child.getName(), child.getTextTrim());
}
}
} catch (DocumentException e) {
e.printStackTrace();
}
return map;
}
public static byte[] getContentBytes(String content, String charset) {
if (charset == null || "".equals(charset)) {
return content.getBytes();
}
try {
return content.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
}
}
/**
* 微信支付统一下单接口
*
* @param orderCode (订单支付单号)商户系统内部订单号,要求32个字符内,只能是数字、大小写字母
* @param totalFee 订单总金额,单位为分
* @param localIp 终端ip地址(非必填)
* @param openId 用户openId
* @param notifyUrl 微信回调地址(非必填)回调地址不能包含参数,具体看微信文档
* @return
* @throws Exception
*/
public static WxPayParams wxPay(String orderCode, String totalFee, String localIp, String openId,
String notifyUrl)
throws Exception {
WxPayParams wxPayParams = new WxPayParams();
//随机字符串
String nonce_str = WxPayUtil.makeUUID(32);
//封装参数
SortedMap<String, String> paramMap = new TreeMap<String, String>();
paramMap.put("appid", APPID);
paramMap.put("mch_id", MCH_ID);
paramMap.put("nonce_str", nonce_str);
paramMap.put("body", BODY);
paramMap.put("out_trade_no", orderCode);
paramMap.put("total_fee", totalFee);
if (StringUtil.isBlank(localIp)) {
paramMap.put("spbill_create_ip", WxPayUtil.getLocalIp());
} else {
paramMap.put("spbill_create_ip", localIp);
}
if (StringUtil.isNotBlank(notifyUrl)) {
paramMap.put("notify_url", notifyUrl);
} else {
paramMap.put("notify_url", "http://www.weixin.qq.com/wxpay/pay.php");
}
paramMap.put("trade_type", TRADE_TYPE_JSAPI);
paramMap.put("openid", openId);
paramMap.put("sign", createSign(paramMap, KEY));
//转换为xml
String s = mapToXml(paramMap);
log.info("微信统一支付参数:" + s);
String post = cn.hutool.http.HttpUtil.post(WX_TYXD_URL, s);
Map<String, String> map = xmlStrToMap(post);
log.info("微信统一支付返回结果:" + map);
//封装二次签名参数
SortedMap<String, String> resultMap = new TreeMap<String, String>();
if (SUCCESS.equals(map.get("return_code")) && OK.equals(map.get("return_msg"))) {
if (FAIL.equals(map.get("result_code"))) {
throw new Exception("FAIL:" + map.get("err_code_des"));
}
resultMap.put("appId", APPID);
resultMap.put("timeStamp", WxPayUtil.getCurrentTimeStamp());
resultMap.put("nonceStr", nonce_str);
resultMap.put("package", "prepay_id=" + map.get("prepay_id"));
resultMap.put("signType", SIGN_TYPE_MD5);
resultMap.put("paySign", createSign(resultMap, KEY));
//统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=
wxPayParams.setWxPackage(resultMap.get("package"));
//随机字符串,长度为32个字符以下
wxPayParams.setNonceStr(resultMap.get("nonceStr"));
//微信支付的paySign(第二次加密结果)
wxPayParams.setPaySign(resultMap.get("paySign"));
//签名加密方式
wxPayParams.setSignType(resultMap.get("signType"));
//时间戳
wxPayParams.setTimeStamp(resultMap.get("timeStamp"));
} else {
throw new Exception("FAIL:" + map.get("return_msg"));
}
return wxPayParams;
}
/**
* 微信支付结果查询
*
* @param outTradeNo 商户订单号
* @param transactionId 微信订单号
*/
public static WxQueryResult orderQuery(String outTradeNo, String transactionId) throws Exception {
if (StringUtil.isBlank(outTradeNo) && StringUtil.isBlank(transactionId)) {
throw new Exception("FAIL:参数不能为空");
}
WxQueryResult wxQueryResult = null;
//封装参数
SortedMap<String, String> paramMap = new TreeMap<String, String>();
if (StringUtil.isNotBlank(transactionId)) {
paramMap.put("transaction_id", transactionId);
} else {
paramMap.put("out_trade_no", outTradeNo);
}
paramMap.put("appid", APPID);
paramMap.put("mch_id", MCH_ID);
paramMap.put("nonce_str", WxPayUtil.makeUUID(32));
paramMap.put("sign", createSign(paramMap, KEY));
log.info("微信支付结果查询参数:" + paramMap);
String s = mapToXml(paramMap);
String post = cn.hutool.http.HttpUtil.post(WX_ORDERQUERY_URL, s);
Map<String, String> map = WxPayUtil.xmlStrToMap(post);
log.info("微信支付结果查询结果:" + map);
if (SUCCESS.equals(map.get("return_code")) && OK.equals(map.get("return_msg"))) {
if (FAIL.equals(map.get("result_code"))) {
throw new Exception("FAIL:" + map.get("err_code_des"));
}
wxQueryResult = new WxQueryResult();
wxQueryResult.setTransactionId(map.get("transaction_id"));
wxQueryResult.setNonceStr(map.get("nonce_str"));
wxQueryResult.setTradeState(map.get("trade_state"));
wxQueryResult.setBankType(map.get("bank_type"));
wxQueryResult.setOpenId(map.get("openid"));
wxQueryResult.setSign(map.get("sign"));
wxQueryResult.setMchId(map.get("mch_id"));
wxQueryResult.setCashFee(map.get("cash_fee"));
wxQueryResult.setOutTradeNo(map.get("out_trade_no"));
wxQueryResult.setAppId(map.get("appid"));
wxQueryResult.setTotalFee(map.get("total_fee"));
wxQueryResult.setTradeStateDesc(map.get("trade_state_desc"));
wxQueryResult.setTradeType(map.get("trade_type"));
wxQueryResult.setResultCode(map.get("result_code"));
wxQueryResult.setTimeEnd(map.get("time_end"));
wxQueryResult.setIsSubscribe(map.get("is_subscribe"));
wxQueryResult.setResultCode(map.get("SUCCESS"));
} else {
throw new Exception("FAIL:" + map.get("return_msg"));
}
return wxQueryResult;
}
}