这几天一直在研究微信支付回调这个问题,发现之前微信支付回调都是正常的也没怎么在意,今天在自己项目上测试的时候发现相同的代码在我这个项目上微信支付回调老是重复执行导致支付成功之后的回调逻辑一直在执行,很头疼。回调逻辑都在执行,说明回调正常执行
网上有些给的答案:
微信没有正常接收到SUCCESS消息建议将resXml:
resXml ="<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
修改为:resXml = "SUCCESS";
也有人反映这个方法可行,但是我自己这边没能执行成功。
也有部分人说修改成
resXml ="<xml>" + "<return_code>SUCCESS</return_code>"+ "<return_msg>OK</return_msg>" + "</xml> ";
同样部分人说可以 我修改了还是不行。
首先看下我的支付回调代码(这边只说微信支付回调,因为到这里应该都是微信支付通了的吧,我也有一篇是写微信支付的,点击这个链接)
/** * @author Mr.Lin * @description 微信支付回调 * @date 2019/7/5 */ @Service public class WeiXinPayNotifyServiceImpl implements WeiXinPayNotifyService { private final static Logger LOGGER = LoggerFactory.getLogger(WeiXinPayNotifyServiceImpl.class); @Override public void weixinpay_notify(HttpServletRequest request, HttpServletResponse response) {
/**
这些是通过微信支付,返回可以得到一些值 具体如下截图 这个代码可以不用管 下面会继续给出成功且不重复回调的代码 这边的支付都是JSAPA支付
*/
InputStream inputStream; StringBuffer sb = new StringBuffer(); try { inputStream = request.getInputStream(); String s; BufferedReader in = new BufferedReader(new InputStreamReader( inputStream, "UTF-8")); while ((s = in.readLine()) != null) { sb.append(s); } in.close(); inputStream.close(); Map<String, String> m = new HashMap<String, String>(); m = WXUtil.doXMLParse(sb.toString()); SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>(); Iterator it = m.keySet().iterator(); while (it.hasNext()) { String parameter = (String) it.next(); String parameterValue = m.get(parameter); String v = ""; if (null != parameterValue) { v = parameterValue.trim(); } packageParams.put(parameter, v); } String key = WxPayPojo.MCH_KEY; // 秘钥 这个就是你的微信商户平台的秘钥 if (WXUtil.isTenpaySign("UTF-8", packageParams, key)) { String resXml = ""; if ("SUCCESS".equals((String) packageParams.get("result_code"))) { // 得到返回的参数 String openid = (String) packageParams.get("openid"); String transaction_id = (String) packageParams .get("transaction_id"); String orderNumberMain = (String) packageParams .get("out_trade_no"); // 这里可以写你需要的业务 LOGGER.debug("我是回调函数啊!---我执行了---------------------"); LOGGER.debug("openid---->" + openid); LOGGER.debug("transaction_id---->" + transaction_id); LOGGER.debug("out_trade_no---->" + orderNumberMain); resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; BufferedOutputStream out = new BufferedOutputStream( response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); return; } else { LOGGER.debug("回调失败"); } } else { LOGGER.debug("回调失败"); } } catch (IOException e) { e.printStackTrace(); } catch (JDOMException e) { e.printStackTrace(); } } }
下面是微信支付回调可以调用的参数,你看你项目会用到什么,一般来说获取到订单编号 通过订单编号操作(点击这里查看微信支付回调JSAPI文档):
即上面的都可以通过packageParams.get(") 对应的到返回值。
下面是我参考一个博客之后修改的,符合我的这个要求:链接如下:https://blog.csdn.net/qq_37105358/article/details/81285779
/** * 微信小程序支付成功回调函数 * @param request * @param response * @throws Exception */ @RequestMapping(value = "/weixinpay/notify") public void wxNotify(HttpServletRequest request,HttpServletResponse response) throws Exception{ BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream)request.getInputStream())); String line = null; StringBuilder sb = new StringBuilder(); while((line = br.readLine()) != null){ sb.append(line); } br.close(); //sb为微信返回的xml String notityXml = sb.toString(); String resXml = ""; System.out.println("接收到的报文:" + notityXml); Map map = PayUtil.doXMLParse(notityXml); String returnCode = (String) map.get("return_code"); if("SUCCESS".equals(returnCode)){ //验证签名是否正确 Map<String, String> validParams = PayUtil.paraFilter(map); //回调验签时需要去除sign和空值参数 String validStr = PayUtil.createLinkString(validParams);//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 String sign = PayUtil.sign(validStr, WxPayPojo.MCH_KEY, "utf-8").toUpperCase();//拼装生成服务器端验证的签名 // 因为微信回调会有八次之多,所以当第一次回调成功了,那么我们就不再执行逻辑了 //根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等 if(sign.equals(map.get("sign"))){ // 得到返回的参数 //这边我上面也说过了 同理 需要什么参数 直接通过map.get获取 参数列表我上面也列举了 String openid = (String) map.get("openid"); String transaction_id = (String) map.get("transaction_id"); String orderNumberMain = (String) map.get("out_trade_no"); /**回调逻辑代码编写*/ //通知微信服务器已经支付成功 resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; } else { System.out.println("微信支付回调失败!签名不一致"); } }else{ resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> "; } System.out.println(resXml); System.out.println("微信支付回调数据结束"); BufferedOutputStream out = new BufferedOutputStream( response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); }
用到的工具类:
import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.security.SignatureException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.codec.digest.DigestUtils; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; public class PayUtil { /** * * 签名字符串 * * @param text 需要签名的字符串 * * @param key 密钥 * * @param input_charset 编码格式 * * @return 签名结果 * */ public static String sign(String text, String key, String input_charset) { text = text + "&key=" + key; return DigestUtils.md5Hex(getContentBytes(text, input_charset)); } /** * * 签名字符串 * * @param text 需要签名的字符串 * * @param sign 签名结果 * * @param key 密钥 * * @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 requestUrl 请求地址 * * @param requestMethod 请求方法 * * @param outputStr 参数 * */ 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; } Map m = new HashMap(); InputStream in = String2Inputstream(strxml); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String k = e.getName(); String v = ""; List children = e.getChildren(); if (children.isEmpty()) { v = e.getTextNormalize(); } else { v = getChildrenText(children); } m.put(k, v); } //关闭流 in.close(); return m; } /** * * 获取子结点的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()); } }
回调的链接 你微信支付的时候 应该也有写到的 就跟前端正常调用接口一样 比如:https://www.cnblogs.com/ 这个是你项目的域名 这个你项目名称lxwt
回调链接就是:https://www.cnblogs.com/lxwt/weixinpay/notify 就可以了
我跟他这个代码对比下发现其实都是差不多的 一个意思 就是不清楚为啥会重复回调 可能是我的工具类解析的问题吧 下面这个我测试过了 不会出现重复回调了
有更好的方法请留言 谢谢!