本人第一篇博客,之前笔记一直放在有道云,想写博客很久了,一直没下定决心,也没静下心好好写,今天突然跟朋友谈 到平时工作根本没时间学习、整理、总结balabala,工作中遇到的问题很多会经常遇到,不整理总结第二次碰到又要半天,就这么扯吧扯吧,扯完之后,不知道哪来的决心,就下手了,哈哈,废话不多说,进入正题啦,有不对的或者更好的建议,希望各位看官指点指点小白~
首先还是要来看下微信官方的api文档,官网的开发步骤以及流程,稍后我会把自己代码的步骤和流程简述,这里就不一一贴图出来
组装统一下单参数
按照参数名ASCII码从小到大排序(字典序)
第一次签名->拼 接XML
发起统一下单请求
解析统一下单返回的结果(xml形式)
(支付成功)将解析得到的预付单信息拼接,再次签名
返回前端签名字符串
public Map<String, Object> weixinPrepay(FirstSignDto data, HttpServletRequest request) {
//CommonSystemConfig为配置类
CommonSystemConfig commonSystemConfig = SysConfigUtils.get(CommonSystemConfig.class);
try {
//准备下单所需要的参数
String noStr = IdGenerator.getRandomString(32);//生成随机的字符串
String notifyUrl = commonSystemConfig.getNotifyUrl(); //回调地址,注意这个地址不要被拦截
String appId = commonSystemConfig.getAppId();//小程序appid
String tradeType = commonSystemConfig.getTradeType();//类型 JSAPI
String merchantNo = commonSystemConfig.getMerchantNo();//商户号
String key = commonSystemConfig.getKey();//支付密钥,在商户那里可以获取
String orderNo = data.getOrderNo();//业务订单号
Double tradeMoney = data.getTradeMoney();//交易金额
String openid = data.getOpenid();//openid
logger.info("---- 订单 {} 预支付 ----", orderNo);
//验证业务订单号是否合法
Order order =orderService.selectOne(new EntityWrapper<Order>().eq("order_no", orderNo));
if (ParamUtil.isNullOrEmptyOrZero(order)) {
LeaseException.throwSystemException(LeaseExceEnums.ORDER_EXCEPTION);
}
//校验该订单的支付金额跟前端传过来的金额是否一致
if (order.getTradeMoney() == tradeMoney) {
LeaseException.throwSystemException(LeaseExceEnums.ORDER_EXCEPTION);
}
//获取终端的ip
String ipAddr = WxUtil.getIpAddr(request);
//页面传来的交易金额单位是 元,转换成 分
String fee = String.valueOf((int) (tradeMoney * 100));
//微信支付必须大于0.01,所以这里个人做了对应业务处理,根据各自需求来处理
if (tradeMoney == 0.00) {
.....................
}
String bodyStr = new String("WashinFUN".getBytes("UTF-8"));
//组装参数,生成下单所需要的参数map进行第一次签名
Map<String, String> packageParams = new HashMap<>();
packageParams.put("appid", appId);
packageParams.put("mch_id", merchantNo);
packageParams.put("nonce_str", noStr);
packageParams.put("notify_url", notifyUrl);
packageParams.put("out_trade_no", orderNo);
packageParams.put("spbill_create_ip", ipAddr);
packageParams.put("total_fee", fee);
packageParams.put("trade_type", tradeType);
packageParams.put("openid", openid);
packageParams.put("body", bodyStr);
//所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串,用来签名
String preStr = PayUtils.createLinkString(packageParams);
logger.info("微信支付preStr:{}", preStr);
//第一次签名
String sign = PayUtils.sign(preStr, key, "utf-8").toUpperCase();
logger.info("微信支付签名sign:{}", sign);
//拼接xml
String xml = "<xml>" + "<appid>" + appId + "</appid>"
+ "<body>" + bodyStr + "</body>"
+ "<mch_id>" + merchantNo + "</mch_id>"
+ "<nonce_str>" + noStr + "</nonce_str>"
+ "<notify_url>" + notifyUrl + "</notify_url>"
+ "<openid>" + openid + "</openid>"
+ "<out_trade_no>" + orderNo + "</out_trade_no>"
+ "<spbill_create_ip>" + ipAddr + "</spbill_create_ip>"
+ "<total_fee>" + fee + "</total_fee>"
+ "<trade_type>" + tradeType + "</trade_type>"
+ "<sign>" + sign + "</sign>"
+ "</xml>";
logger.info("统一下单接口 XML数据:{}", xml);
String result = PayUtils.httpRequest(PAY_URL, "POST", xml);
logger.info("统一下单结果:{}", result);
//解析xml
Map map = PayUtils.doXMLParse(result);
String return_code = (String) map.get("return_code");//返回状态码
Map<String, Object> response = new HashMap<String, Object>();//返回给小程序端需要的参数map
if (return_code.equals("SUCCESS")) {
String prepay_id = (String) map.get("prepay_id");//返回的预付单信息
response.put("nonceStr", noStr);
response.put("package", "prepay_id=" + prepay_id);
Long timeStamp = System.currentTimeMillis() / 1000;
response.put("timeStamp", timeStamp + "");//这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
//拼接签名需要的参数
String stringSignTemp = "appId=" + appId + "&nonceStr=" + noStr + "&package=prepay_id=" + prepay_id + "&signType=MD5&timeStamp=" + timeStamp;
//第二次签名,这个签名的结果用于小程序调用接口(wx.requesetPayment)
String paySign = PayUtils.sign(stringSignTemp, key, "utf-8").toUpperCase();
Order order = orderService.selectOne(new EntityWrapper<Order>().eq("order_no", orderNo));
if (!ParamUtil.isNullOrEmptyOrZero(order)) {
Date tradeTime = new Date();
order.setTradeTime(tradeTime);
order.setTradeScene(commonSystemConfig.getTradeType());
orderService.updateById(order);
logger.info("支付成功,设置订单表 {} 的交易时间 {} 交易场景 {} :", orderNo, tradeTime, tradeType);
}
response.put("paySign", paySign);
}
response.put("appid", appId);
response.put("bypassPayStatus", 0);//这是业务需要所返回的字段
return response;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
PayUtils
public class PayUtils {
/**
* 签名字符串
*
* @param
* @param key 密钥
* @param
* @return 签名结果
*/
public static String sign(String text, String key, String input_charset) {
text = text + "&key=" + key;
return DigestUtils.md5Hex(getContentBytes(text, input_charset));
}
public static String signature(Map<String, String> map, String key) {
Set<String> keySet = map.keySet();
String[] str = new String[map.size()];
StringBuilder tmp = new StringBuilder();
// 进行字典排序
str = keySet.toArray(str);
Arrays.sort(str);
for (int i = 0; i < str.length; i++) {
String t = str[i] + "=" + map.get(str[i]) + "&";
tmp.append(t);
}
if (null != key) {
tmp.append("key=" + key);
}
return DigestUtils.md5Hex(tmp.toString()).toUpperCase();
}
/**
* 签名字符串
*
* @param
* @param sign 签名结果
* @param
* @param input_charset 编码格式
* @return 签名结果
*/
public static boolean verify(String text, String sign, String key, String input_charset) {
text = text + key;
String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset));
if (mysign.equals(sign)) {
return true;
} else {
return false;
}
}
/**
* @param content
* @param charset
* @return
* @throws SignatureException
* @throws UnsupportedEncodingException
*/
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);
}
}
private static boolean isValidChar(char ch) {
if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
return true;
if ((ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f))
return true;// 简体中文汉字编码
return false;
}
/**
* 除去数组中的空值和签名参数
*
* @param sArray 签名参数组
* @return 去掉空值与签名参数后的新签名参数组
*/
public static Map<String, String> paraFilter(Map<String, String> sArray) {
Map<String, String> result = new HashMap<String, String>();
if (sArray == null || sArray.size() <= 0) {
return result;
}
for (String key : sArray.keySet()) {
String value = sArray.get(key);
if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
|| key.equalsIgnoreCase("sign_type")) {
continue;
}
result.put(key, value);
}
return result;
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params) {
List<String> keys = new ArrayList<String>(params.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
prestr = prestr + key + "=" + value;
} else {
prestr = prestr + key + "=" + value + "&";
}
}
return prestr;
}
/**
* @param
* @param
* @param
*/
public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
// 创建SSLContext
StringBuffer buffer = null;
try {
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(requestMethod);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();
//往服务器端写内容
if (null != outputStr) {
OutputStream os = conn.getOutputStream();
os.write(outputStr.getBytes("utf-8"));
os.close();
}
// 读取服务器端返回的内容
InputStream is = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
buffer = new StringBuffer();
String line = null;
while ((line = br.readLine()) != null) {
buffer.append(line);
}
br.close();
} catch (Exception e) {
e.printStackTrace();
}
return buffer.toString();
}
public static String urlEncodeUTF8(String source) {
String result = source;
try {
result = java.net.URLEncoder.encode(source, "UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
*
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static Map doXMLParse(String strxml) throws Exception {
if (null == strxml || "".equals(strxml)) {
return null;
}
/*============= !!!!注意,修复了微信官方反馈的漏洞,更新于2018-10-16 ===========*/
try {
Map<String, String> data = new HashMap<String, String>();
// TODO 在这里更换
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
InputStream stream = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilderFactory.newDocumentBuilder().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) {
// do nothing
}
return data;
} catch (Exception ex) {
throw ex;
}
}
/**
* 获取子结点的xml
*
* @param children
* @return String
*/
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if (!children.isEmpty()) {
Iterator it = children.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if (!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
public static InputStream String2Inputstream(String str) {
return new ByteArrayInputStream(str.getBytes());
}
}
1、签名时一定要按照接口定义的规则签名,字必须的字段名称,交易金额的单位,签名的时候,可以使用微信的工具 微信接口调试工具 来验证,代码里的签名要跟工具生成的签名一致 2、支付密钥key是否有效正确,第一次拿错了,博主在这里卡了比较久,支付key错误导致统一下单报错:<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名错误]]></return_msg></xml> 3、按照文中步骤,微信支付亲测可用,文中如有错误或者不合理,欢迎留言,微信退款会在后续献上